@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":"animate-confetti.js","sourceRoot":"","sources":["../src/animate-confetti.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\ninterface Particle {\n color: string;\n x: number;\n y: number;\n diameter: number;\n tilt: number;\n tiltAngleIncrement: number;\n tiltAngle: number;\n}\n\n/**\n * Fullscreen confetti animation overlay, rendered on a fixed-position canvas.\n * Starts automatically on first render and stops after `duration` ms.\n *\n * @element animate-confetti\n */\n@customElement(\"animate-confetti\")\nexport class AnimateConfetti extends LitElement {\n /** How long the confetti streams before stopping, in ms. */\n @property({ type: Number })\n duration: number = 6000;\n\n private maxParticleCount = 150; // set max confetti count\n private particleSpeed = 2; // set the particle animation speed\n\n private particles: Particle[] = [];\n private streamingConfetti: boolean = false;\n private colors = [\n \"DodgerBlue\",\n \"OliveDrab\",\n \"Gold\",\n \"Pink\",\n \"SlateBlue\",\n \"LightBlue\",\n \"Violet\",\n \"PaleGreen\",\n \"SteelBlue\",\n \"SandyBrown\",\n \"Chocolate\",\n \"Crimson\",\n ];\n private waveAngle: number = 0.0;\n private animationTimer?: number;\n\n override connectedCallback() {\n super.connectedCallback();\n window.addEventListener(\"resize\", this._handleResize);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n window.removeEventListener(\"resize\", this._handleResize);\n this.stopConfetti();\n }\n\n static override styles = css`\n #confetti-canvas {\n display: block;\n position: fixed;\n top: 0;\n left: 0;\n z-index: 999999;\n pointer-events: none;\n }\n `;\n\n /**\n * @returns The canvas element\n */\n private get _canvas(): HTMLCanvasElement {\n return <HTMLCanvasElement>this.shadowRoot!.getElementById(\"confetti-canvas\")!;\n }\n\n /**\n * Resize canvas to browser size.\n */\n private _handleResize = () => {\n const canvas = this._canvas;\n canvas.width = window.innerWidth;\n canvas.height = window.innerHeight;\n };\n\n /**\n * Start confetti once shadow dom has been rendered for the first time.\n */\n protected override firstUpdated(): void {\n this.startConfetti();\n setTimeout(() => {\n this.stopConfetti();\n }, this.duration);\n }\n\n override render() {\n return html`<canvas id=\"confetti-canvas\"></canvas>`;\n }\n\n private startConfetti() {\n const width = window.innerWidth;\n const height = window.innerHeight;\n this._handleResize();\n\n while (this.particles.length < this.maxParticleCount)\n this.particles.push(this.resetParticle({} as Particle, width, height));\n this.streamingConfetti = true;\n if (this.animationTimer == undefined) {\n this.runAnimation();\n }\n }\n\n private runAnimation = () => {\n const canvas = this._canvas;\n const context = canvas.getContext(\"2d\");\n context!.clearRect(0, 0, window.innerWidth, window.innerHeight);\n if (this.particles.length === 0) this.animationTimer = undefined;\n else {\n this.updateParticles();\n this.drawParticles(context);\n this.animationTimer = window.requestAnimationFrame(this.runAnimation);\n }\n };\n\n /**\n * Stop confetti and clean up after reasonable time.\n */\n private stopConfetti() {\n this.streamingConfetti = false;\n setTimeout(this._cleanup, 5000);\n }\n\n /**\n * Removes any associated particle data from internal storage to regain memory.\n */\n private _cleanup(): void {\n this.particles = [];\n }\n\n private updateParticles() {\n const width = window.innerWidth;\n const height = window.innerHeight;\n let particle: Particle;\n this.waveAngle += 0.01;\n for (let i = 0; i < this.particles.length; i++) {\n particle = this.particles[i];\n if (!this.streamingConfetti && particle.y < -15) particle.y = height + 100;\n else {\n particle.tiltAngle += particle.tiltAngleIncrement;\n particle.x += Math.sin(this.waveAngle);\n particle.y += (Math.cos(this.waveAngle) + particle.diameter + this.particleSpeed) * 0.5;\n particle.tilt = Math.sin(particle.tiltAngle) * 15;\n }\n if (particle.x > width + 20 || particle.x < -20 || particle.y > height) {\n if (this.streamingConfetti && this.particles.length <= this.maxParticleCount)\n this.resetParticle(particle, width, height);\n else {\n this.particles.splice(i, 1);\n i--;\n }\n }\n }\n }\n\n private drawParticles(context: CanvasRenderingContext2D | null) {\n if (!context) return;\n let particle: Particle;\n let x: number;\n for (let i = 0; i < this.particles.length; i++) {\n particle = this.particles[i];\n context.beginPath();\n context.lineWidth = particle.diameter;\n context.strokeStyle = particle.color;\n x = particle.x + particle.tilt;\n context.moveTo(x + particle.diameter / 2, particle.y);\n context.lineTo(x, particle.y + particle.tilt + particle.diameter / 2);\n context.stroke();\n }\n }\n\n private resetParticle(particle: Particle, width: number, height: number): Particle {\n particle.color = this.colors[(Math.random() * this.colors.length) | 0];\n particle.x = Math.random() * width;\n particle.y = Math.random() * height - height;\n particle.diameter = Math.random() * 10 + 5;\n particle.tilt = Math.random() * 10 - 10;\n particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05;\n particle.tiltAngle = 0;\n return particle;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"animate-confetti\": AnimateConfetti;\n }\n}\n"]}
1
+ {"version":3,"file":"animate-confetti.js","sourceRoot":"","sources":["../src/animate-confetti.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\ninterface Particle {\n color: string;\n x: number;\n y: number;\n diameter: number;\n tilt: number;\n tiltAngleIncrement: number;\n tiltAngle: number;\n}\n\n/**\n * Fullscreen confetti animation overlay, rendered on a fixed-position canvas.\n * Starts automatically on first render and stops after `duration` ms.\n *\n * @element animate-confetti\n */\n@customElement(\"animate-confetti\")\nexport class AnimateConfetti extends LitElement {\n /** How long the confetti streams before stopping, in ms. */\n @property({ type: Number })\n duration: number = 6000;\n\n private maxParticleCount = 150; // set max confetti count\n private particleSpeed = 2; // set the particle animation speed\n\n private particles: Particle[] = [];\n private streamingConfetti: boolean = false;\n private colors = [\n \"DodgerBlue\",\n \"OliveDrab\",\n \"Gold\",\n \"Pink\",\n \"SlateBlue\",\n \"LightBlue\",\n \"Violet\",\n \"PaleGreen\",\n \"SteelBlue\",\n \"SandyBrown\",\n \"Chocolate\",\n \"Crimson\",\n ];\n private waveAngle: number = 0.0;\n private animationTimer?: number;\n private stopTimer?: number;\n private cleanupTimer?: number;\n\n override connectedCallback() {\n super.connectedCallback();\n window.addEventListener(\"resize\", this._handleResize);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n window.removeEventListener(\"resize\", this._handleResize);\n this._clearTimersAndParticles();\n }\n\n static override styles = [\n tokens,\n css`\n #confetti-canvas {\n display: block;\n position: fixed;\n top: 0;\n left: 0;\n z-index: 999999;\n pointer-events: none;\n }\n `,\n ];\n\n /**\n * @returns The canvas element\n */\n private get _canvas(): HTMLCanvasElement {\n return <HTMLCanvasElement>this.shadowRoot!.getElementById(\"confetti-canvas\")!;\n }\n\n /**\n * Resize canvas to browser size.\n */\n private _handleResize = () => {\n const canvas = this._canvas;\n canvas.width = window.innerWidth;\n canvas.height = window.innerHeight;\n };\n\n /**\n * Start confetti once shadow dom has been rendered for the first time.\n */\n protected override firstUpdated(): void {\n this._handleResize();\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n this.startConfetti();\n this.stopTimer = window.setTimeout(() => {\n this.stopTimer = undefined;\n this.stopConfetti();\n }, this.duration);\n }\n\n override render() {\n return html`<canvas id=\"confetti-canvas\" aria-hidden=\"true\"></canvas>`;\n }\n\n /** Creates particles and starts the animation frame loop. */\n private startConfetti() {\n const width = window.innerWidth;\n const height = window.innerHeight;\n this._handleResize();\n\n while (this.particles.length < this.maxParticleCount)\n this.particles.push(this.resetParticle({} as Particle, width, height));\n this.streamingConfetti = true;\n if (this.animationTimer == undefined) {\n this.runAnimation();\n }\n }\n\n private runAnimation = () => {\n const canvas = this._canvas;\n const context = canvas.getContext(\"2d\");\n context!.clearRect(0, 0, window.innerWidth, window.innerHeight);\n if (this.particles.length === 0) this.animationTimer = undefined;\n else {\n this.updateParticles();\n this.drawParticles(context);\n this.animationTimer = window.requestAnimationFrame(this.runAnimation);\n }\n };\n\n /**\n * Stop confetti and clean up after reasonable time.\n */\n private stopConfetti() {\n this.streamingConfetti = false;\n this.cleanupTimer = window.setTimeout(this._cleanup, 5000);\n }\n\n /** Removes particle data and stops the animation frame loop. */\n private readonly _cleanup = (): void => {\n this.particles = [];\n if (this.animationTimer !== undefined) {\n window.cancelAnimationFrame(this.animationTimer);\n this.animationTimer = undefined;\n }\n const canvas = this.shadowRoot?.getElementById(\"confetti-canvas\") as HTMLCanvasElement | null;\n canvas?.getContext(\"2d\")?.clearRect(0, 0, canvas.width, canvas.height);\n this.cleanupTimer = undefined;\n };\n\n /** Cancels all pending work when the component leaves the document. */\n private _clearTimersAndParticles(): void {\n if (this.stopTimer !== undefined) {\n window.clearTimeout(this.stopTimer);\n this.stopTimer = undefined;\n }\n if (this.cleanupTimer !== undefined) {\n window.clearTimeout(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n this.streamingConfetti = false;\n this._cleanup();\n }\n\n private updateParticles() {\n const width = window.innerWidth;\n const height = window.innerHeight;\n let particle: Particle;\n this.waveAngle += 0.01;\n for (let i = 0; i < this.particles.length; i++) {\n particle = this.particles[i];\n if (!this.streamingConfetti && particle.y < -15) particle.y = height + 100;\n else {\n particle.tiltAngle += particle.tiltAngleIncrement;\n particle.x += Math.sin(this.waveAngle);\n particle.y += (Math.cos(this.waveAngle) + particle.diameter + this.particleSpeed) * 0.5;\n particle.tilt = Math.sin(particle.tiltAngle) * 15;\n }\n if (particle.x > width + 20 || particle.x < -20 || particle.y > height) {\n if (this.streamingConfetti && this.particles.length <= this.maxParticleCount)\n this.resetParticle(particle, width, height);\n else {\n this.particles.splice(i, 1);\n i--;\n }\n }\n }\n }\n\n private drawParticles(context: CanvasRenderingContext2D | null) {\n if (!context) return;\n let particle: Particle;\n let x: number;\n for (let i = 0; i < this.particles.length; i++) {\n particle = this.particles[i];\n context.beginPath();\n context.lineWidth = particle.diameter;\n context.strokeStyle = particle.color;\n x = particle.x + particle.tilt;\n context.moveTo(x + particle.diameter / 2, particle.y);\n context.lineTo(x, particle.y + particle.tilt + particle.diameter / 2);\n context.stroke();\n }\n }\n\n private resetParticle(particle: Particle, width: number, height: number): Particle {\n particle.color = this.colors[(Math.random() * this.colors.length) | 0];\n particle.x = Math.random() * width;\n particle.y = Math.random() * height - height;\n particle.diameter = Math.random() * 10 + 5;\n particle.tilt = Math.random() * 10 - 10;\n particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05;\n particle.tiltAngle = 0;\n return particle;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"animate-confetti\": AnimateConfetti;\n }\n}\n"]}
@@ -36,6 +36,8 @@ export declare class AutocompleteInput extends LitElement {
36
36
  placeholder: string;
37
37
  /** Marks the input as required for native form validation. */
38
38
  required: boolean;
39
+ /** Disables the input and closes its suggestion popup. */
40
+ disabled: boolean;
39
41
  /** API endpoint queried in API mode. Ignored when `options` is set. */
40
42
  endpoint: string;
41
43
  /** Query string parameter name the current input text is sent under. */
@@ -53,15 +55,21 @@ export declare class AutocompleteInput extends LitElement {
53
55
  private _suggestions;
54
56
  private _open;
55
57
  private _activeIndex;
58
+ private _formDisabled;
56
59
  /** Last-picked option, or null once the input diverges from it. */
57
60
  get selectedOption(): AutocompleteOption | null;
61
+ protected willUpdate(changed: PropertyValues): void;
58
62
  protected updated(changed: PropertyValues): void;
59
63
  disconnectedCallback(): void;
60
64
  /** Resets to empty on `form.reset()`, per the form-associated custom element contract. */
61
65
  formResetCallback(): void;
66
+ /** Mirrors ancestor fieldset disabled state onto the native input. */
67
+ formDisabledCallback(disabled: boolean): void;
62
68
  private onInput;
63
69
  private onKeydown;
64
70
  private onBlur;
71
+ private onCompositionStart;
72
+ private onCompositionEnd;
65
73
  private renderSuggestions;
66
74
  render(): import("lit-html").TemplateResult<1>;
67
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"autocomplete-input.d.ts","sourceRoot":"","sources":["../src/autocomplete-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAI1E,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBACa,iBAAkB,SAAQ,UAAU;;IAC/C,MAAM,CAAC,cAAc,UAAQ;IAE7B,OAAgB,MAAM,4BA0CpB;IAEF,mFAAmF;IACvE,KAAK,SAAM;IACvB,sDAAsD;IAC1C,WAAW,SAAM;IAC7B,8DAA8D;IACjC,QAAQ,UAAS;IAC9C,uEAAuE;IAC3D,QAAQ,SAAM;IAC1B,wEAAwE;IAChC,UAAU,SAAO;IACzD,uEAAuE;IAC3C,QAAQ,SAAO;IAC3C,yDAAyD;IACJ,SAAS,SAAK;IACnE;;;;OAIG;IAC6B,OAAO,EAAE,kBAAkB,EAAE,GAAG,IAAI,CAAQ;IAEnE,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAM;IAOnC,mEAAmE;IACnE,IAAI,cAAc,IAAI,kBAAkB,GAAG,IAAI,CAG9C;IAED,UAAmB,OAAO,CAAC,OAAO,EAAE,cAAc,QAEjD;IAEQ,oBAAoB,SAI5B;IAED,0FAA0F;IAC1F,iBAAiB,SAKhB;IAED,OAAO,CAAC,OAAO;IA0Df,OAAO,CAAC,SAAS;IA2BjB,OAAO,CAAC,MAAM;IAkBd,OAAO,CAAC,iBAAiB;IAoBhB,MAAM,yCAgBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,iBAAiB,CAAC;KACzC;CACF"}
1
+ {"version":3,"file":"autocomplete-input.d.ts","sourceRoot":"","sources":["../src/autocomplete-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAO1E,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBACa,iBAAkB,SAAQ,UAAU;;IAC/C,MAAM,CAAC,cAAc,UAAQ;IAE7B,OAAgB,MAAM,4BAoFpB;IAEF,mFAAmF;IACvE,KAAK,SAAM;IACvB,sDAAsD;IAC1C,WAAW,SAAM;IAC7B,8DAA8D;IACjC,QAAQ,UAAS;IAC9C,0DAA0D;IACd,QAAQ,UAAS;IAC7D,uEAAuE;IAC3D,QAAQ,SAAM;IAC1B,wEAAwE;IAChC,UAAU,SAAO;IACzD,uEAAuE;IAC3C,QAAQ,SAAO;IAC3C,yDAAyD;IACJ,SAAS,SAAK;IACnE;;;;OAIG;IAC6B,OAAO,EAAE,kBAAkB,EAAE,GAAG,IAAI,CAAQ;IAEnE,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAS;IAgBvC,mEAAmE;IACnE,IAAI,cAAc,IAAI,kBAAkB,GAAG,IAAI,CAG9C;IAED,UAAmB,UAAU,CAAC,OAAO,EAAE,cAAc,QAQpD;IAED,UAAmB,OAAO,CAAC,OAAO,EAAE,cAAc,QAEjD;IAEQ,oBAAoB,SAK5B;IAED,0FAA0F;IAC1F,iBAAiB,SAKhB;IAED,sEAAsE;IACtE,oBAAoB,CAAC,QAAQ,EAAE,OAAO,QAQrC;IAED,OAAO,CAAC,OAAO;IA8Df,OAAO,CAAC,SAAS;IAuCjB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,gBAAgB;IAgCxB,OAAO,CAAC,iBAAiB;IA0BhB,MAAM,yCA6Bd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,iBAAiB,CAAC;KACzC;CACF"}
@@ -7,6 +7,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { LitElement, css, html, nothing } from "lit";
8
8
  import { customElement, property, state } from "lit/decorators.js";
9
9
  import { tokens } from "./tokens.js";
10
+ import { submitWithDefaultButton } from "./utils/form.js";
11
+ let instanceCount = 0;
10
12
  /**
11
13
  * Generic form-associated text input with a suggestion dropdown, for any
12
14
  * "type to filter a list of `{key, value}` options" use case. Works as a
@@ -38,6 +40,8 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
38
40
  this.placeholder = "";
39
41
  /** Marks the input as required for native form validation. */
40
42
  this.required = false;
43
+ /** Disables the input and closes its suggestion popup. */
44
+ this.disabled = false;
41
45
  /** API endpoint queried in API mode. Ignored when `options` is set. */
42
46
  this.endpoint = "";
43
47
  /** Query string parameter name the current input text is sent under. */
@@ -55,10 +59,15 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
55
59
  this._suggestions = [];
56
60
  this._open = false;
57
61
  this._activeIndex = -1;
62
+ this._formDisabled = false;
58
63
  this.#internals = this.attachInternals();
59
64
  this.#lastPicked = null;
60
65
  this.#debounceTimer = null;
61
66
  this.#abortController = null;
67
+ this.#isComposing = false;
68
+ this.#compositionJustEnded = false;
69
+ this.#compositionEndTimer = null;
70
+ this.#listboxId = `autocomplete-input-listbox-${++instanceCount}`;
62
71
  }
63
72
  static { this.formAssociated = true; }
64
73
  static { this.styles = [
@@ -69,14 +78,34 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
69
78
  position: relative;
70
79
  }
71
80
  input {
72
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
81
+ font-family: var(
82
+ --ui-font,
83
+ ui-sans-serif,
84
+ system-ui,
85
+ sans-serif,
86
+ "Apple Color Emoji",
87
+ "Segoe UI Emoji",
88
+ "Segoe UI Symbol",
89
+ "Noto Color Emoji"
90
+ );
73
91
  font-size: var(--ui-font-size-sm, 0.75rem);
74
- padding: 0.35rem 0.5rem;
92
+ color: var(--ui-text, #0f172a);
93
+ background: var(--ui-surface, #ffffff);
94
+ padding: 0.5rem;
75
95
  border: 1px solid var(--ui-border, #e2e8f0);
76
96
  border-radius: var(--ui-radius-sm, 0.25rem);
77
97
  width: 100%;
78
98
  box-sizing: border-box;
79
99
  }
100
+ input:disabled {
101
+ cursor: not-allowed;
102
+ opacity: 0.6;
103
+ }
104
+ input:focus-visible {
105
+ outline: none;
106
+ border-color: var(--ui-primary, #4f46e5);
107
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
108
+ }
80
109
  .suggestions {
81
110
  position: absolute;
82
111
  top: 100%;
@@ -85,16 +114,16 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
85
114
  z-index: 10;
86
115
  max-height: 40vh;
87
116
  overflow-y: auto;
88
- margin: 2px 0 0;
89
- padding: 4px 0;
117
+ margin: 0.25rem 0 0;
118
+ padding: 0.25rem 0;
90
119
  list-style: none;
91
- background: var(--ui-surface, #fff);
120
+ background: var(--ui-surface, #ffffff);
92
121
  border: 1px solid var(--ui-border, #e2e8f0);
93
122
  border-radius: var(--ui-radius-sm, 0.25rem);
94
123
  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));
95
124
  }
96
125
  .suggestion {
97
- padding: 0.4rem 0.6rem;
126
+ padding: 0.5rem 0.75rem;
98
127
  font-size: var(--ui-font-size-sm, 0.75rem);
99
128
  cursor: pointer;
100
129
  }
@@ -102,18 +131,58 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
102
131
  .suggestion:hover {
103
132
  background: var(--ui-surface-muted, #f8fafc);
104
133
  }
134
+ .no-suggestions {
135
+ padding: 0.5rem 0.75rem;
136
+ color: var(--ui-text-muted, #64748b);
137
+ font-size: var(--ui-font-size-sm, 0.75rem);
138
+ font-style: italic;
139
+ }
140
+ @media (forced-colors: active) {
141
+ input:focus-visible {
142
+ outline: 2px solid CanvasText;
143
+ outline-offset: 2px;
144
+ box-shadow: none;
145
+ }
146
+ input:disabled {
147
+ color: GrayText;
148
+ opacity: 1;
149
+ }
150
+ .suggestion.active,
151
+ .suggestion:hover {
152
+ color: HighlightText;
153
+ background: Highlight;
154
+ }
155
+ }
105
156
  `,
106
157
  ]; }
107
158
  #internals;
108
159
  #lastPicked;
109
160
  #debounceTimer;
110
161
  #abortController;
162
+ #isComposing;
163
+ #compositionJustEnded;
164
+ #compositionEndTimer;
165
+ #listboxId;
166
+ /** Whether the host or an ancestor fieldset currently disables the control. */
167
+ get #isDisabled() {
168
+ return this.disabled || this._formDisabled;
169
+ }
111
170
  /** Last-picked option, or null once the input diverges from it. */
112
171
  get selectedOption() {
113
172
  if (this.#lastPicked && this.#lastPicked.value === this.value)
114
173
  return this.#lastPicked;
115
174
  return null;
116
175
  }
176
+ willUpdate(changed) {
177
+ if (changed.has("disabled") && this.#isDisabled) {
178
+ if (this.#debounceTimer)
179
+ clearTimeout(this.#debounceTimer);
180
+ this.#resetComposition();
181
+ this.#abortController?.abort();
182
+ this._open = false;
183
+ this._activeIndex = -1;
184
+ }
185
+ }
117
186
  updated(changed) {
118
187
  if (changed.has("value"))
119
188
  this.#internals.setFormValue(this.value);
@@ -122,6 +191,7 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
122
191
  super.disconnectedCallback();
123
192
  if (this.#debounceTimer)
124
193
  clearTimeout(this.#debounceTimer);
194
+ this.#resetComposition();
125
195
  this.#abortController?.abort();
126
196
  }
127
197
  /** Resets to empty on `form.reset()`, per the form-associated custom element contract. */
@@ -131,17 +201,34 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
131
201
  this._open = false;
132
202
  this.#lastPicked = null;
133
203
  }
204
+ /** Mirrors ancestor fieldset disabled state onto the native input. */
205
+ formDisabledCallback(disabled) {
206
+ this._formDisabled = disabled;
207
+ if (!disabled)
208
+ return;
209
+ if (this.#debounceTimer)
210
+ clearTimeout(this.#debounceTimer);
211
+ this.#resetComposition();
212
+ this.#abortController?.abort();
213
+ this._open = false;
214
+ this._activeIndex = -1;
215
+ }
134
216
  onInput(e) {
217
+ if (this.#isDisabled)
218
+ return;
135
219
  this.value = e.target.value;
136
220
  this.#lastPicked = null;
137
221
  this.#scheduleFetch(this.value);
138
222
  }
139
223
  #scheduleFetch(query) {
224
+ if (this.#isDisabled)
225
+ return;
140
226
  if (this.#debounceTimer)
141
227
  clearTimeout(this.#debounceTimer);
142
228
  if (query.trim().length < this.minLength) {
143
229
  this._suggestions = [];
144
230
  this._open = false;
231
+ this._activeIndex = -1;
145
232
  return;
146
233
  }
147
234
  if (this.options) {
@@ -159,7 +246,7 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
159
246
  .filter((o) => o.value.toLowerCase().includes(needle))
160
247
  .slice(0, 5);
161
248
  this._activeIndex = -1;
162
- this._open = this._suggestions.length > 0;
249
+ this._open = !this.#isDisabled;
163
250
  }
164
251
  /** Fetches options for `query` from `endpoint`, cancelling any request still in flight. */
165
252
  async #fetchOptions(query) {
@@ -170,9 +257,11 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
170
257
  const res = await fetch(this.#buildUrl(query), { signal: controller.signal });
171
258
  if (!res.ok)
172
259
  throw new Error(`autocomplete request failed: ${res.status}`);
260
+ if (this.#isDisabled)
261
+ return;
173
262
  this._suggestions = (await res.json()).slice(0, 5);
174
263
  this._activeIndex = -1;
175
- this._open = this._suggestions.length > 0;
264
+ this._open = true;
176
265
  }
177
266
  catch {
178
267
  // Aborted (superseded by newer keystroke) or a network failure while
@@ -188,6 +277,11 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
188
277
  return `${this.endpoint}?${params.toString()}`;
189
278
  }
190
279
  onKeydown(e) {
280
+ if (this.#isDisabled)
281
+ return;
282
+ if (e.isComposing || e.keyCode === 229 || this.#isComposing || this.#compositionJustEnded) {
283
+ return;
284
+ }
191
285
  if (e.key === "ArrowDown")
192
286
  this.#moveActive(1, e);
193
287
  else if (e.key === "ArrowUp")
@@ -205,12 +299,19 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
205
299
  this._activeIndex = (this._activeIndex + delta + n) % n;
206
300
  }
207
301
  #handleEnter(e) {
208
- if (!this._open)
209
- return; // closed list: let the form submit normally
210
- e.preventDefault();
211
302
  const pick = this._suggestions[this._activeIndex] ?? this._suggestions[0];
212
- if (pick)
303
+ if (this._open && pick) {
304
+ e.preventDefault();
213
305
  this.#selectOption(pick);
306
+ return;
307
+ }
308
+ if (this.#internals.form) {
309
+ const form = this.#internals.form;
310
+ window.setTimeout(() => {
311
+ if (!e.defaultPrevented && form.isConnected)
312
+ submitWithDefaultButton(form);
313
+ });
314
+ }
214
315
  }
215
316
  #handleEscape(e) {
216
317
  if (!this._open)
@@ -221,6 +322,31 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
221
322
  onBlur() {
222
323
  this._open = false;
223
324
  }
325
+ onCompositionStart() {
326
+ if (this.#compositionEndTimer)
327
+ clearTimeout(this.#compositionEndTimer);
328
+ this.#compositionEndTimer = null;
329
+ this.#compositionJustEnded = false;
330
+ this.#isComposing = true;
331
+ }
332
+ onCompositionEnd() {
333
+ this.#isComposing = false;
334
+ this.#compositionJustEnded = true;
335
+ if (this.#compositionEndTimer)
336
+ clearTimeout(this.#compositionEndTimer);
337
+ this.#compositionEndTimer = setTimeout(() => {
338
+ this.#compositionJustEnded = false;
339
+ this.#compositionEndTimer = null;
340
+ });
341
+ }
342
+ /** Clears composition state when the control can no longer receive keys. */
343
+ #resetComposition() {
344
+ if (this.#compositionEndTimer)
345
+ clearTimeout(this.#compositionEndTimer);
346
+ this.#compositionEndTimer = null;
347
+ this.#isComposing = false;
348
+ this.#compositionJustEnded = false;
349
+ }
224
350
  #selectOption(o) {
225
351
  this.value = o.value;
226
352
  this.#lastPicked = o;
@@ -234,12 +360,13 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
234
360
  this.#selectOption(o);
235
361
  }
236
362
  renderSuggestions() {
237
- if (!this._open || this._suggestions.length === 0)
363
+ if (!this._open || this.#isDisabled)
238
364
  return nothing;
239
365
  return html `
240
- <ul class="suggestions" role="listbox">
366
+ <ul id=${this.#listboxId} class="suggestions" role="listbox" aria-label="Suggestions">
241
367
  ${this._suggestions.map((o, i) => html `
242
368
  <li
369
+ id=${`${this.#listboxId}-option-${i}`}
243
370
  role="option"
244
371
  aria-selected=${i === this._activeIndex}
245
372
  class="suggestion ${i === this._activeIndex ? "active" : ""}"
@@ -248,19 +375,36 @@ let AutocompleteInput = class AutocompleteInput extends LitElement {
248
375
  ${o.value}
249
376
  </li>
250
377
  `)}
378
+ ${this._suggestions.length === 0
379
+ ? html `<li class="no-suggestions" role="presentation">
380
+ <span role="status">No suggestions found</span>
381
+ </li>`
382
+ : nothing}
251
383
  </ul>
252
384
  `;
253
385
  }
254
386
  render() {
387
+ const activeDescendant = this._open && !this.#isDisabled && this._activeIndex >= 0
388
+ ? `${this.#listboxId}-option-${this._activeIndex}`
389
+ : nothing;
390
+ const expanded = this._open && !this.#isDisabled;
255
391
  return html `
256
- <div role="combobox" aria-expanded=${this._open} aria-haspopup="listbox">
392
+ <div>
257
393
  <input
258
394
  type="text"
395
+ role="combobox"
396
+ aria-autocomplete="list"
397
+ aria-expanded=${expanded}
398
+ aria-controls=${this.#listboxId}
399
+ aria-activedescendant=${activeDescendant}
259
400
  .value=${this.value}
260
401
  placeholder=${this.placeholder}
261
402
  ?required=${this.required}
403
+ ?disabled=${this.#isDisabled}
262
404
  autocomplete="off"
263
405
  @input=${this.onInput}
406
+ @compositionstart=${this.onCompositionStart}
407
+ @compositionend=${this.onCompositionEnd}
264
408
  @keydown=${this.onKeydown}
265
409
  @blur=${this.onBlur}
266
410
  />
@@ -278,6 +422,9 @@ __decorate([
278
422
  __decorate([
279
423
  property({ type: Boolean })
280
424
  ], AutocompleteInput.prototype, "required", void 0);
425
+ __decorate([
426
+ property({ type: Boolean, reflect: true })
427
+ ], AutocompleteInput.prototype, "disabled", void 0);
281
428
  __decorate([
282
429
  property()
283
430
  ], AutocompleteInput.prototype, "endpoint", void 0);
@@ -302,6 +449,9 @@ __decorate([
302
449
  __decorate([
303
450
  state()
304
451
  ], AutocompleteInput.prototype, "_activeIndex", void 0);
452
+ __decorate([
453
+ state()
454
+ ], AutocompleteInput.prototype, "_formDisabled", void 0);
305
455
  AutocompleteInput = __decorate([
306
456
  customElement("autocomplete-input")
307
457
  ], AutocompleteInput);
@@ -1 +1 @@
1
- {"version":3,"file":"autocomplete-input.js","sourceRoot":"","sources":["../src/autocomplete-input.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 { tokens } from \"./tokens.js\";\n\n/** A single autocomplete candidate: an opaque `key` plus its display `value`. */\nexport interface AutocompleteOption {\n key: string;\n value: string;\n}\n\n/**\n * Generic form-associated text input with a suggestion dropdown, for any\n * \"type to filter a list of `{key, value}` options\" use case. Works as a\n * drop-in replacement for a plain `<input>` inside a `<form>`: set `name` on\n * the element itself and consumers keep reading\n * `new FormData(form).get(name)` and calling `form.reset()` unchanged — the\n * submitted value is the picked option's `value`, while `key` is available\n * via the `option-select` event and the `selectedOption` getter for cases\n * that need the underlying id rather than the display text.\n *\n * Supports two suggestion sources:\n * - **API**: set `endpoint` to a URL that, given a `?<query-param>=<text>`\n * query string, responds with a JSON array of `{key, value}` objects.\n * Requests are debounced as the user types.\n * - **Local array**: set `options` to a fixed `AutocompleteOption[]` and the\n * component filters it client-side instead of making any network\n * request. Useful for small/fixed lists, offline use, or tests. Takes\n * priority over `endpoint` whenever it's non-null.\n *\n * @element autocomplete-input\n * @fires option-select - An option was picked; detail: AutocompleteOption.\n */\n@customElement(\"autocomplete-input\")\nexport class AutocompleteInput extends LitElement {\n static formAssociated = true;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n position: relative;\n }\n input {\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size-sm, 0.75rem);\n padding: 0.35rem 0.5rem;\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius-sm, 0.25rem);\n width: 100%;\n box-sizing: border-box;\n }\n .suggestions {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n z-index: 10;\n max-height: 40vh;\n overflow-y: auto;\n margin: 2px 0 0;\n padding: 4px 0;\n list-style: none;\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 .suggestion {\n padding: 0.4rem 0.6rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n cursor: pointer;\n }\n .suggestion.active,\n .suggestion:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n `,\n ];\n\n /** Current input value; also the form-associated value submitted with the form. */\n @property() value = \"\";\n /** Placeholder text shown when the input is empty. */\n @property() placeholder = \"\";\n /** Marks the input as required for native form validation. */\n @property({ type: Boolean }) required = false;\n /** API endpoint queried in API mode. Ignored when `options` is set. */\n @property() endpoint = \"\";\n /** Query string parameter name the current input text is sent under. */\n @property({ attribute: \"query-param\" }) queryParam = \"q\";\n /** Debounce delay (ms) before firing a request/filter after typing. */\n @property({ type: Number }) debounce = 300;\n /** Minimum query length before suggestions are shown. */\n @property({ type: Number, attribute: \"min-length\" }) minLength = 3;\n /**\n * Fixed candidate list to filter locally instead of fetching from\n * `endpoint`. When set (non-null), no network request is ever made —\n * this takes priority over the API mode.\n */\n @property({ attribute: false }) options: AutocompleteOption[] | null = null;\n\n @state() private _suggestions: AutocompleteOption[] = [];\n @state() private _open = false;\n @state() private _activeIndex = -1;\n\n #internals = this.attachInternals();\n #lastPicked: AutocompleteOption | null = null;\n #debounceTimer: ReturnType<typeof setTimeout> | null = null;\n #abortController: AbortController | null = null;\n\n /** Last-picked option, or null once the input diverges from it. */\n get selectedOption(): AutocompleteOption | null {\n if (this.#lastPicked && this.#lastPicked.value === this.value) return this.#lastPicked;\n return null;\n }\n\n protected override updated(changed: PropertyValues) {\n if (changed.has(\"value\")) this.#internals.setFormValue(this.value);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n if (this.#debounceTimer) clearTimeout(this.#debounceTimer);\n this.#abortController?.abort();\n }\n\n /** Resets to empty on `form.reset()`, per the form-associated custom element contract. */\n formResetCallback() {\n this.value = \"\";\n this._suggestions = [];\n this._open = false;\n this.#lastPicked = null;\n }\n\n private onInput(e: InputEvent) {\n this.value = (e.target as HTMLInputElement).value;\n this.#lastPicked = null;\n this.#scheduleFetch(this.value);\n }\n\n #scheduleFetch(query: string) {\n if (this.#debounceTimer) clearTimeout(this.#debounceTimer);\n if (query.trim().length < this.minLength) {\n this._suggestions = [];\n this._open = false;\n return;\n }\n if (this.options) {\n // Local mode: filtering is synchronous and cheap, but keep the same\n // debounce as the API mode so the UX doesn't shift between sources.\n this.#debounceTimer = setTimeout(() => this.#filterLocalOptions(query), this.debounce);\n return;\n }\n this.#debounceTimer = setTimeout(() => void this.#fetchOptions(query), this.debounce);\n }\n\n /** Filters the locally-supplied `options` list for `query`, case-insensitively. */\n #filterLocalOptions(query: string) {\n const needle = query.trim().toLowerCase();\n this._suggestions = (this.options ?? [])\n .filter((o) => o.value.toLowerCase().includes(needle))\n .slice(0, 5);\n this._activeIndex = -1;\n this._open = this._suggestions.length > 0;\n }\n\n /** Fetches options for `query` from `endpoint`, cancelling any request still in flight. */\n async #fetchOptions(query: string) {\n this.#abortController?.abort();\n const controller = new AbortController();\n this.#abortController = controller;\n try {\n const res = await fetch(this.#buildUrl(query), { signal: controller.signal });\n if (!res.ok) throw new Error(`autocomplete request failed: ${res.status}`);\n this._suggestions = ((await res.json()) as AutocompleteOption[]).slice(0, 5);\n this._activeIndex = -1;\n this._open = this._suggestions.length > 0;\n } catch {\n // Aborted (superseded by newer keystroke) or a network failure while\n // typing — fail silently and just close the list.\n if (!controller.signal.aborted) {\n this._suggestions = [];\n this._open = false;\n }\n }\n }\n\n #buildUrl(query: string): string {\n const params = new URLSearchParams({ [this.queryParam]: query });\n return `${this.endpoint}?${params.toString()}`;\n }\n\n private onKeydown(e: KeyboardEvent) {\n if (e.key === \"ArrowDown\") this.#moveActive(1, e);\n else if (e.key === \"ArrowUp\") this.#moveActive(-1, e);\n else if (e.key === \"Enter\") this.#handleEnter(e);\n else if (e.key === \"Escape\") this.#handleEscape(e);\n }\n\n #moveActive(delta: number, e: KeyboardEvent) {\n if (!this._open || this._suggestions.length === 0) return;\n e.preventDefault();\n const n = this._suggestions.length;\n this._activeIndex = (this._activeIndex + delta + n) % n;\n }\n\n #handleEnter(e: KeyboardEvent) {\n if (!this._open) return; // closed list: let the form submit normally\n e.preventDefault();\n const pick = this._suggestions[this._activeIndex] ?? this._suggestions[0];\n if (pick) this.#selectOption(pick);\n }\n\n #handleEscape(e: KeyboardEvent) {\n if (!this._open) return;\n e.preventDefault();\n this._open = false;\n }\n\n private onBlur() {\n this._open = false;\n }\n\n #selectOption(o: AutocompleteOption) {\n this.value = o.value;\n this.#lastPicked = o;\n this._open = false;\n this._suggestions = [];\n this.dispatchEvent(new CustomEvent<AutocompleteOption>(\"option-select\", { detail: o, bubbles: true, composed: true }));\n }\n\n /** Selects on mousedown (not click) so it wins over the input's blur, which closes the list. */\n #onSuggestionMousedown(e: MouseEvent, o: AutocompleteOption) {\n e.preventDefault();\n this.#selectOption(o);\n }\n\n private renderSuggestions() {\n if (!this._open || this._suggestions.length === 0) return nothing;\n return html`\n <ul class=\"suggestions\" role=\"listbox\">\n ${this._suggestions.map(\n (o, i) => html`\n <li\n role=\"option\"\n aria-selected=${i === this._activeIndex}\n class=\"suggestion ${i === this._activeIndex ? \"active\" : \"\"}\"\n @mousedown=${(e: MouseEvent) => this.#onSuggestionMousedown(e, o)}\n >\n ${o.value}\n </li>\n `,\n )}\n </ul>\n `;\n }\n\n override render() {\n return html`\n <div role=\"combobox\" aria-expanded=${this._open} aria-haspopup=\"listbox\">\n <input\n type=\"text\"\n .value=${this.value}\n placeholder=${this.placeholder}\n ?required=${this.required}\n autocomplete=\"off\"\n @input=${this.onInput}\n @keydown=${this.onKeydown}\n @blur=${this.onBlur}\n />\n ${this.renderSuggestions()}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"autocomplete-input\": AutocompleteInput;\n }\n}\n"]}
1
+ {"version":3,"file":"autocomplete-input.js","sourceRoot":"","sources":["../src/autocomplete-input.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 { tokens } from \"./tokens.js\";\nimport { submitWithDefaultButton } from \"./utils/form.js\";\n\nlet instanceCount = 0;\n\n/** A single autocomplete candidate: an opaque `key` plus its display `value`. */\nexport interface AutocompleteOption {\n key: string;\n value: string;\n}\n\n/**\n * Generic form-associated text input with a suggestion dropdown, for any\n * \"type to filter a list of `{key, value}` options\" use case. Works as a\n * drop-in replacement for a plain `<input>` inside a `<form>`: set `name` on\n * the element itself and consumers keep reading\n * `new FormData(form).get(name)` and calling `form.reset()` unchanged — the\n * submitted value is the picked option's `value`, while `key` is available\n * via the `option-select` event and the `selectedOption` getter for cases\n * that need the underlying id rather than the display text.\n *\n * Supports two suggestion sources:\n * - **API**: set `endpoint` to a URL that, given a `?<query-param>=<text>`\n * query string, responds with a JSON array of `{key, value}` objects.\n * Requests are debounced as the user types.\n * - **Local array**: set `options` to a fixed `AutocompleteOption[]` and the\n * component filters it client-side instead of making any network\n * request. Useful for small/fixed lists, offline use, or tests. Takes\n * priority over `endpoint` whenever it's non-null.\n *\n * @element autocomplete-input\n * @fires option-select - An option was picked; detail: AutocompleteOption.\n */\n@customElement(\"autocomplete-input\")\nexport class AutocompleteInput extends LitElement {\n static formAssociated = true;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n position: relative;\n }\n input {\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 font-size: var(--ui-font-size-sm, 0.75rem);\n color: var(--ui-text, #0f172a);\n background: var(--ui-surface, #ffffff);\n padding: 0.5rem;\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius-sm, 0.25rem);\n width: 100%;\n box-sizing: border-box;\n }\n input:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n }\n input:focus-visible {\n outline: none;\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 .suggestions {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n z-index: 10;\n max-height: 40vh;\n overflow-y: auto;\n margin: 0.25rem 0 0;\n padding: 0.25rem 0;\n list-style: none;\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 .suggestion {\n padding: 0.5rem 0.75rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n cursor: pointer;\n }\n .suggestion.active,\n .suggestion:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .no-suggestions {\n padding: 0.5rem 0.75rem;\n color: var(--ui-text-muted, #64748b);\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-style: italic;\n }\n @media (forced-colors: active) {\n input:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n input:disabled {\n color: GrayText;\n opacity: 1;\n }\n .suggestion.active,\n .suggestion:hover {\n color: HighlightText;\n background: Highlight;\n }\n }\n `,\n ];\n\n /** Current input value; also the form-associated value submitted with the form. */\n @property() value = \"\";\n /** Placeholder text shown when the input is empty. */\n @property() placeholder = \"\";\n /** Marks the input as required for native form validation. */\n @property({ type: Boolean }) required = false;\n /** Disables the input and closes its suggestion popup. */\n @property({ type: Boolean, reflect: true }) disabled = false;\n /** API endpoint queried in API mode. Ignored when `options` is set. */\n @property() endpoint = \"\";\n /** Query string parameter name the current input text is sent under. */\n @property({ attribute: \"query-param\" }) queryParam = \"q\";\n /** Debounce delay (ms) before firing a request/filter after typing. */\n @property({ type: Number }) debounce = 300;\n /** Minimum query length before suggestions are shown. */\n @property({ type: Number, attribute: \"min-length\" }) minLength = 3;\n /**\n * Fixed candidate list to filter locally instead of fetching from\n * `endpoint`. When set (non-null), no network request is ever made —\n * this takes priority over the API mode.\n */\n @property({ attribute: false }) options: AutocompleteOption[] | null = null;\n\n @state() private _suggestions: AutocompleteOption[] = [];\n @state() private _open = false;\n @state() private _activeIndex = -1;\n @state() private _formDisabled = false;\n\n #internals = this.attachInternals();\n #lastPicked: AutocompleteOption | null = null;\n #debounceTimer: ReturnType<typeof setTimeout> | null = null;\n #abortController: AbortController | null = null;\n #isComposing = false;\n #compositionJustEnded = false;\n #compositionEndTimer: ReturnType<typeof setTimeout> | null = null;\n readonly #listboxId = `autocomplete-input-listbox-${++instanceCount}`;\n\n /** Whether the host or an ancestor fieldset currently disables the control. */\n get #isDisabled(): boolean {\n return this.disabled || this._formDisabled;\n }\n\n /** Last-picked option, or null once the input diverges from it. */\n get selectedOption(): AutocompleteOption | null {\n if (this.#lastPicked && this.#lastPicked.value === this.value) return this.#lastPicked;\n return null;\n }\n\n protected override willUpdate(changed: PropertyValues) {\n if (changed.has(\"disabled\") && this.#isDisabled) {\n if (this.#debounceTimer) clearTimeout(this.#debounceTimer);\n this.#resetComposition();\n this.#abortController?.abort();\n this._open = false;\n this._activeIndex = -1;\n }\n }\n\n protected override updated(changed: PropertyValues) {\n if (changed.has(\"value\")) this.#internals.setFormValue(this.value);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n if (this.#debounceTimer) clearTimeout(this.#debounceTimer);\n this.#resetComposition();\n this.#abortController?.abort();\n }\n\n /** Resets to empty on `form.reset()`, per the form-associated custom element contract. */\n formResetCallback() {\n this.value = \"\";\n this._suggestions = [];\n this._open = false;\n this.#lastPicked = null;\n }\n\n /** Mirrors ancestor fieldset disabled state onto the native input. */\n formDisabledCallback(disabled: boolean) {\n this._formDisabled = disabled;\n if (!disabled) return;\n if (this.#debounceTimer) clearTimeout(this.#debounceTimer);\n this.#resetComposition();\n this.#abortController?.abort();\n this._open = false;\n this._activeIndex = -1;\n }\n\n private onInput(e: InputEvent) {\n if (this.#isDisabled) return;\n this.value = (e.target as HTMLInputElement).value;\n this.#lastPicked = null;\n this.#scheduleFetch(this.value);\n }\n\n #scheduleFetch(query: string) {\n if (this.#isDisabled) return;\n if (this.#debounceTimer) clearTimeout(this.#debounceTimer);\n if (query.trim().length < this.minLength) {\n this._suggestions = [];\n this._open = false;\n this._activeIndex = -1;\n return;\n }\n if (this.options) {\n // Local mode: filtering is synchronous and cheap, but keep the same\n // debounce as the API mode so the UX doesn't shift between sources.\n this.#debounceTimer = setTimeout(() => this.#filterLocalOptions(query), this.debounce);\n return;\n }\n this.#debounceTimer = setTimeout(() => void this.#fetchOptions(query), this.debounce);\n }\n\n /** Filters the locally-supplied `options` list for `query`, case-insensitively. */\n #filterLocalOptions(query: string) {\n const needle = query.trim().toLowerCase();\n this._suggestions = (this.options ?? [])\n .filter((o) => o.value.toLowerCase().includes(needle))\n .slice(0, 5);\n this._activeIndex = -1;\n this._open = !this.#isDisabled;\n }\n\n /** Fetches options for `query` from `endpoint`, cancelling any request still in flight. */\n async #fetchOptions(query: string) {\n this.#abortController?.abort();\n const controller = new AbortController();\n this.#abortController = controller;\n try {\n const res = await fetch(this.#buildUrl(query), { signal: controller.signal });\n if (!res.ok) throw new Error(`autocomplete request failed: ${res.status}`);\n if (this.#isDisabled) return;\n this._suggestions = ((await res.json()) as AutocompleteOption[]).slice(0, 5);\n this._activeIndex = -1;\n this._open = true;\n } catch {\n // Aborted (superseded by newer keystroke) or a network failure while\n // typing — fail silently and just close the list.\n if (!controller.signal.aborted) {\n this._suggestions = [];\n this._open = false;\n }\n }\n }\n\n #buildUrl(query: string): string {\n const params = new URLSearchParams({ [this.queryParam]: query });\n return `${this.endpoint}?${params.toString()}`;\n }\n\n private onKeydown(e: KeyboardEvent) {\n if (this.#isDisabled) return;\n if (e.isComposing || e.keyCode === 229 || this.#isComposing || this.#compositionJustEnded) {\n return;\n }\n if (e.key === \"ArrowDown\") this.#moveActive(1, e);\n else if (e.key === \"ArrowUp\") this.#moveActive(-1, e);\n else if (e.key === \"Enter\") this.#handleEnter(e);\n else if (e.key === \"Escape\") this.#handleEscape(e);\n }\n\n #moveActive(delta: number, e: KeyboardEvent) {\n if (!this._open || this._suggestions.length === 0) return;\n e.preventDefault();\n const n = this._suggestions.length;\n this._activeIndex = (this._activeIndex + delta + n) % n;\n }\n\n #handleEnter(e: KeyboardEvent) {\n const pick = this._suggestions[this._activeIndex] ?? this._suggestions[0];\n if (this._open && pick) {\n e.preventDefault();\n this.#selectOption(pick);\n return;\n }\n if (this.#internals.form) {\n const form = this.#internals.form;\n window.setTimeout(() => {\n if (!e.defaultPrevented && form.isConnected) submitWithDefaultButton(form);\n });\n }\n }\n\n #handleEscape(e: KeyboardEvent) {\n if (!this._open) return;\n e.preventDefault();\n this._open = false;\n }\n\n private onBlur() {\n this._open = false;\n }\n\n private onCompositionStart(): void {\n if (this.#compositionEndTimer) clearTimeout(this.#compositionEndTimer);\n this.#compositionEndTimer = null;\n this.#compositionJustEnded = false;\n this.#isComposing = true;\n }\n\n private onCompositionEnd(): void {\n this.#isComposing = false;\n this.#compositionJustEnded = true;\n if (this.#compositionEndTimer) clearTimeout(this.#compositionEndTimer);\n this.#compositionEndTimer = setTimeout(() => {\n this.#compositionJustEnded = false;\n this.#compositionEndTimer = null;\n });\n }\n\n /** Clears composition state when the control can no longer receive keys. */\n #resetComposition(): void {\n if (this.#compositionEndTimer) clearTimeout(this.#compositionEndTimer);\n this.#compositionEndTimer = null;\n this.#isComposing = false;\n this.#compositionJustEnded = false;\n }\n\n #selectOption(o: AutocompleteOption) {\n this.value = o.value;\n this.#lastPicked = o;\n this._open = false;\n this._suggestions = [];\n this.dispatchEvent(new CustomEvent<AutocompleteOption>(\"option-select\", { detail: o, bubbles: true, composed: true }));\n }\n\n /** Selects on mousedown (not click) so it wins over the input's blur, which closes the list. */\n #onSuggestionMousedown(e: MouseEvent, o: AutocompleteOption) {\n e.preventDefault();\n this.#selectOption(o);\n }\n\n private renderSuggestions() {\n if (!this._open || this.#isDisabled) return nothing;\n return html`\n <ul id=${this.#listboxId} class=\"suggestions\" role=\"listbox\" aria-label=\"Suggestions\">\n ${this._suggestions.map(\n (o, i) => html`\n <li\n id=${`${this.#listboxId}-option-${i}`}\n role=\"option\"\n aria-selected=${i === this._activeIndex}\n class=\"suggestion ${i === this._activeIndex ? \"active\" : \"\"}\"\n @mousedown=${(e: MouseEvent) => this.#onSuggestionMousedown(e, o)}\n >\n ${o.value}\n </li>\n `,\n )}\n ${this._suggestions.length === 0\n ? html`<li class=\"no-suggestions\" role=\"presentation\">\n <span role=\"status\">No suggestions found</span>\n </li>`\n : nothing}\n </ul>\n `;\n }\n\n override render() {\n const activeDescendant =\n this._open && !this.#isDisabled && this._activeIndex >= 0\n ? `${this.#listboxId}-option-${this._activeIndex}`\n : nothing;\n const expanded = this._open && !this.#isDisabled;\n return html`\n <div>\n <input\n type=\"text\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=${expanded}\n aria-controls=${this.#listboxId}\n aria-activedescendant=${activeDescendant}\n .value=${this.value}\n placeholder=${this.placeholder}\n ?required=${this.required}\n ?disabled=${this.#isDisabled}\n autocomplete=\"off\"\n @input=${this.onInput}\n @compositionstart=${this.onCompositionStart}\n @compositionend=${this.onCompositionEnd}\n @keydown=${this.onKeydown}\n @blur=${this.onBlur}\n />\n ${this.renderSuggestions()}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"autocomplete-input\": AutocompleteInput;\n }\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { LitElement } from "lit";
2
+ import type { StatusPillColor } from "./status-pill.js";
3
+ /**
4
+ * Declarative metadata for one calendar event, consumed by a parent
5
+ * `calendar-month` or `calendar-year`. Read-only/non-interactive; renders
6
+ * nothing itself.
7
+ *
8
+ * @element calendar-entry
9
+ * @slot title - Plain-text title shown instead of the `label` fallback.
10
+ * @slot detail - Repeatable plain-text details rendered inside the shared body spanning all remaining days.
11
+ * @slot footer - Plain-text ending note pinned to the bottom of the shared body.
12
+ */
13
+ export declare class CalendarEntry extends LitElement {
14
+ /** Inclusive start date, `"YYYY-MM-DD"`. */
15
+ start: string;
16
+ /** Inclusive end date, `"YYYY-MM-DD"`. Falls back to `start` when unset (single-day entry). */
17
+ end: string;
18
+ /** Fallback title used when no `title` slot is provided. */
19
+ label: string;
20
+ /** Color variant, reusing `status-pill`'s palette. */
21
+ color: StatusPillColor;
22
+ /** Optional link target; the parent renders the entry as an `<a>` when set. */
23
+ href?: string;
24
+ static styles: import("lit").CSSResult[];
25
+ /** Keeps the metadata host hidden while exposing named text slots to the parent calendar. */
26
+ protected render(): import("lit-html").TemplateResult<1>;
27
+ }
28
+ declare global {
29
+ interface HTMLElementTagNameMap {
30
+ "calendar-entry": CalendarEntry;
31
+ }
32
+ }
33
+ //# sourceMappingURL=calendar-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar-entry.d.ts","sourceRoot":"","sources":["../src/calendar-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAE5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGxD;;;;;;;;;GASG;AACH,qBACa,aAAc,SAAQ,UAAU;IAC3C,4CAA4C;IACf,KAAK,SAAM;IAExC,+FAA+F;IAClE,GAAG,SAAM;IAEtC,4DAA4D;IAC/B,KAAK,SAAM;IAExC,sDAAsD;IACzB,KAAK,EAAE,eAAe,CAAa;IAEhE,+EAA+E;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IAE3C,OAAgB,MAAM,4BAOpB;IAEF,6FAA6F;IAC7F,UAAmB,MAAM,yCAExB;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,aAAa,CAAC;KACjC;CACF"}
@@ -0,0 +1,64 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html } from "lit";
8
+ import { customElement, property } from "lit/decorators.js";
9
+ import { tokens } from "./tokens.js";
10
+ /**
11
+ * Declarative metadata for one calendar event, consumed by a parent
12
+ * `calendar-month` or `calendar-year`. Read-only/non-interactive; renders
13
+ * nothing itself.
14
+ *
15
+ * @element calendar-entry
16
+ * @slot title - Plain-text title shown instead of the `label` fallback.
17
+ * @slot detail - Repeatable plain-text details rendered inside the shared body spanning all remaining days.
18
+ * @slot footer - Plain-text ending note pinned to the bottom of the shared body.
19
+ */
20
+ let CalendarEntry = class CalendarEntry extends LitElement {
21
+ constructor() {
22
+ super(...arguments);
23
+ /** Inclusive start date, `"YYYY-MM-DD"`. */
24
+ this.start = "";
25
+ /** Inclusive end date, `"YYYY-MM-DD"`. Falls back to `start` when unset (single-day entry). */
26
+ this.end = "";
27
+ /** Fallback title used when no `title` slot is provided. */
28
+ this.label = "";
29
+ /** Color variant, reusing `status-pill`'s palette. */
30
+ this.color = "neutral";
31
+ }
32
+ static { this.styles = [
33
+ tokens,
34
+ css `
35
+ :host {
36
+ display: none;
37
+ }
38
+ `,
39
+ ]; }
40
+ /** Keeps the metadata host hidden while exposing named text slots to the parent calendar. */
41
+ render() {
42
+ return html `<slot name="title"></slot><slot name="detail"></slot><slot name="footer"></slot>`;
43
+ }
44
+ };
45
+ __decorate([
46
+ property({ reflect: true })
47
+ ], CalendarEntry.prototype, "start", void 0);
48
+ __decorate([
49
+ property({ reflect: true })
50
+ ], CalendarEntry.prototype, "end", void 0);
51
+ __decorate([
52
+ property({ reflect: true })
53
+ ], CalendarEntry.prototype, "label", void 0);
54
+ __decorate([
55
+ property({ reflect: true })
56
+ ], CalendarEntry.prototype, "color", void 0);
57
+ __decorate([
58
+ property({ reflect: true })
59
+ ], CalendarEntry.prototype, "href", void 0);
60
+ CalendarEntry = __decorate([
61
+ customElement("calendar-entry")
62
+ ], CalendarEntry);
63
+ export { CalendarEntry };
64
+ //# sourceMappingURL=calendar-entry.js.map