@f-ewald/components 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +147 -0
  3. package/custom-elements.json +3398 -0
  4. package/dist/address-autocomplete.d.ts +75 -0
  5. package/dist/address-autocomplete.d.ts.map +1 -0
  6. package/dist/address-autocomplete.js +342 -0
  7. package/dist/address-autocomplete.js.map +1 -0
  8. package/dist/animate-confetti.d.ts +53 -0
  9. package/dist/animate-confetti.d.ts.map +1 -0
  10. package/dist/animate-confetti.js +180 -0
  11. package/dist/animate-confetti.js.map +1 -0
  12. package/dist/autocomplete-input.d.ts +73 -0
  13. package/dist/autocomplete-input.d.ts.map +1 -0
  14. package/dist/autocomplete-input.js +309 -0
  15. package/dist/autocomplete-input.js.map +1 -0
  16. package/dist/confirm-dialog.d.ts +36 -0
  17. package/dist/confirm-dialog.d.ts.map +1 -0
  18. package/dist/confirm-dialog.js +191 -0
  19. package/dist/confirm-dialog.js.map +1 -0
  20. package/dist/copy-link-button.d.ts +27 -0
  21. package/dist/copy-link-button.d.ts.map +1 -0
  22. package/dist/copy-link-button.js +84 -0
  23. package/dist/copy-link-button.js.map +1 -0
  24. package/dist/distance-value.d.ts +24 -0
  25. package/dist/distance-value.d.ts.map +1 -0
  26. package/dist/distance-value.js +50 -0
  27. package/dist/distance-value.js.map +1 -0
  28. package/dist/distribution-chart.d.ts +52 -0
  29. package/dist/distribution-chart.d.ts.map +1 -0
  30. package/dist/distribution-chart.js +299 -0
  31. package/dist/distribution-chart.js.map +1 -0
  32. package/dist/icons.d.ts +32 -0
  33. package/dist/icons.d.ts.map +1 -0
  34. package/dist/icons.js +35 -0
  35. package/dist/icons.js.map +1 -0
  36. package/dist/index.d.ts +18 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +18 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/percent-bar-chart.d.ts +29 -0
  41. package/dist/percent-bar-chart.d.ts.map +1 -0
  42. package/dist/percent-bar-chart.js +92 -0
  43. package/dist/percent-bar-chart.js.map +1 -0
  44. package/dist/price-history-chart.d.ts +39 -0
  45. package/dist/price-history-chart.d.ts.map +1 -0
  46. package/dist/price-history-chart.js +217 -0
  47. package/dist/price-history-chart.js.map +1 -0
  48. package/dist/relative-time.d.ts +26 -0
  49. package/dist/relative-time.d.ts.map +1 -0
  50. package/dist/relative-time.js +64 -0
  51. package/dist/relative-time.js.map +1 -0
  52. package/dist/reveal-button.d.ts +19 -0
  53. package/dist/reveal-button.d.ts.map +1 -0
  54. package/dist/reveal-button.js +62 -0
  55. package/dist/reveal-button.js.map +1 -0
  56. package/dist/roman-numeral.d.ts +18 -0
  57. package/dist/roman-numeral.d.ts.map +1 -0
  58. package/dist/roman-numeral.js +54 -0
  59. package/dist/roman-numeral.js.map +1 -0
  60. package/dist/slide-panel.d.ts +27 -0
  61. package/dist/slide-panel.d.ts.map +1 -0
  62. package/dist/slide-panel.js +149 -0
  63. package/dist/slide-panel.js.map +1 -0
  64. package/dist/toast-notification.d.ts +53 -0
  65. package/dist/toast-notification.d.ts.map +1 -0
  66. package/dist/toast-notification.js +173 -0
  67. package/dist/toast-notification.js.map +1 -0
  68. package/dist/tokens.css +21 -0
  69. package/dist/tokens.d.ts +16 -0
  70. package/dist/tokens.d.ts.map +1 -0
  71. package/dist/tokens.js +79 -0
  72. package/dist/tokens.js.map +1 -0
  73. package/dist/utils/distance.d.ts +11 -0
  74. package/dist/utils/distance.d.ts.map +1 -0
  75. package/dist/utils/distance.js +25 -0
  76. package/dist/utils/distance.js.map +1 -0
  77. package/dist/utils/time.d.ts +10 -0
  78. package/dist/utils/time.d.ts.map +1 -0
  79. package/dist/utils/time.js +33 -0
  80. package/dist/utils/time.js.map +1 -0
  81. package/dist/weight-bar-chart.d.ts +29 -0
  82. package/dist/weight-bar-chart.d.ts.map +1 -0
  83. package/dist/weight-bar-chart.js +96 -0
  84. package/dist/weight-bar-chart.js.map +1 -0
  85. package/docs/address-autocomplete.md +68 -0
  86. package/docs/animate-confetti.md +34 -0
  87. package/docs/autocomplete-input.md +79 -0
  88. package/docs/confirm-dialog.md +65 -0
  89. package/docs/copy-link-button.md +46 -0
  90. package/docs/distance-value.md +38 -0
  91. package/docs/distribution-chart.md +51 -0
  92. package/docs/mcp-evaluation.md +31 -0
  93. package/docs/percent-bar-chart.md +40 -0
  94. package/docs/price-history-chart.md +44 -0
  95. package/docs/relative-time.md +37 -0
  96. package/docs/reveal-button.md +43 -0
  97. package/docs/roman-numeral.md +33 -0
  98. package/docs/slide-panel.md +56 -0
  99. package/docs/toast-notification.md +52 -0
  100. package/docs/weight-bar-chart.md +53 -0
  101. package/llms.txt +361 -0
  102. package/package.json +69 -0
@@ -0,0 +1,180 @@
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
+ /**
10
+ * Fullscreen confetti animation overlay, rendered on a fixed-position canvas.
11
+ * Starts automatically on first render and stops after `duration` ms.
12
+ *
13
+ * @element animate-confetti
14
+ */
15
+ let AnimateConfetti = class AnimateConfetti extends LitElement {
16
+ constructor() {
17
+ super(...arguments);
18
+ /** How long the confetti streams before stopping, in ms. */
19
+ this.duration = 6000;
20
+ this.maxParticleCount = 150; // set max confetti count
21
+ this.particleSpeed = 2; // set the particle animation speed
22
+ this.particles = [];
23
+ this.streamingConfetti = false;
24
+ this.colors = [
25
+ "DodgerBlue",
26
+ "OliveDrab",
27
+ "Gold",
28
+ "Pink",
29
+ "SlateBlue",
30
+ "LightBlue",
31
+ "Violet",
32
+ "PaleGreen",
33
+ "SteelBlue",
34
+ "SandyBrown",
35
+ "Chocolate",
36
+ "Crimson",
37
+ ];
38
+ this.waveAngle = 0.0;
39
+ /**
40
+ * Resize canvas to browser size.
41
+ */
42
+ this._handleResize = () => {
43
+ const canvas = this._canvas;
44
+ canvas.width = window.innerWidth;
45
+ canvas.height = window.innerHeight;
46
+ };
47
+ this.runAnimation = () => {
48
+ const canvas = this._canvas;
49
+ const context = canvas.getContext("2d");
50
+ context.clearRect(0, 0, window.innerWidth, window.innerHeight);
51
+ if (this.particles.length === 0)
52
+ this.animationTimer = undefined;
53
+ else {
54
+ this.updateParticles();
55
+ this.drawParticles(context);
56
+ this.animationTimer = window.requestAnimationFrame(this.runAnimation);
57
+ }
58
+ };
59
+ }
60
+ connectedCallback() {
61
+ super.connectedCallback();
62
+ window.addEventListener("resize", this._handleResize);
63
+ }
64
+ disconnectedCallback() {
65
+ super.disconnectedCallback();
66
+ window.removeEventListener("resize", this._handleResize);
67
+ this.stopConfetti();
68
+ }
69
+ static { this.styles = css `
70
+ #confetti-canvas {
71
+ display: block;
72
+ position: fixed;
73
+ top: 0;
74
+ left: 0;
75
+ z-index: 999999;
76
+ pointer-events: none;
77
+ }
78
+ `; }
79
+ /**
80
+ * @returns The canvas element
81
+ */
82
+ get _canvas() {
83
+ return this.shadowRoot.getElementById("confetti-canvas");
84
+ }
85
+ /**
86
+ * Start confetti once shadow dom has been rendered for the first time.
87
+ */
88
+ firstUpdated() {
89
+ this.startConfetti();
90
+ setTimeout(() => {
91
+ this.stopConfetti();
92
+ }, this.duration);
93
+ }
94
+ render() {
95
+ return html `<canvas id="confetti-canvas"></canvas>`;
96
+ }
97
+ startConfetti() {
98
+ const width = window.innerWidth;
99
+ const height = window.innerHeight;
100
+ this._handleResize();
101
+ while (this.particles.length < this.maxParticleCount)
102
+ this.particles.push(this.resetParticle({}, width, height));
103
+ this.streamingConfetti = true;
104
+ if (this.animationTimer == undefined) {
105
+ this.runAnimation();
106
+ }
107
+ }
108
+ /**
109
+ * Stop confetti and clean up after reasonable time.
110
+ */
111
+ stopConfetti() {
112
+ this.streamingConfetti = false;
113
+ setTimeout(this._cleanup, 5000);
114
+ }
115
+ /**
116
+ * Removes any associated particle data from internal storage to regain memory.
117
+ */
118
+ _cleanup() {
119
+ this.particles = [];
120
+ }
121
+ updateParticles() {
122
+ const width = window.innerWidth;
123
+ const height = window.innerHeight;
124
+ let particle;
125
+ this.waveAngle += 0.01;
126
+ for (let i = 0; i < this.particles.length; i++) {
127
+ particle = this.particles[i];
128
+ if (!this.streamingConfetti && particle.y < -15)
129
+ particle.y = height + 100;
130
+ else {
131
+ particle.tiltAngle += particle.tiltAngleIncrement;
132
+ particle.x += Math.sin(this.waveAngle);
133
+ particle.y += (Math.cos(this.waveAngle) + particle.diameter + this.particleSpeed) * 0.5;
134
+ particle.tilt = Math.sin(particle.tiltAngle) * 15;
135
+ }
136
+ if (particle.x > width + 20 || particle.x < -20 || particle.y > height) {
137
+ if (this.streamingConfetti && this.particles.length <= this.maxParticleCount)
138
+ this.resetParticle(particle, width, height);
139
+ else {
140
+ this.particles.splice(i, 1);
141
+ i--;
142
+ }
143
+ }
144
+ }
145
+ }
146
+ drawParticles(context) {
147
+ if (!context)
148
+ return;
149
+ let particle;
150
+ let x;
151
+ for (let i = 0; i < this.particles.length; i++) {
152
+ particle = this.particles[i];
153
+ context.beginPath();
154
+ context.lineWidth = particle.diameter;
155
+ context.strokeStyle = particle.color;
156
+ x = particle.x + particle.tilt;
157
+ context.moveTo(x + particle.diameter / 2, particle.y);
158
+ context.lineTo(x, particle.y + particle.tilt + particle.diameter / 2);
159
+ context.stroke();
160
+ }
161
+ }
162
+ resetParticle(particle, width, height) {
163
+ particle.color = this.colors[(Math.random() * this.colors.length) | 0];
164
+ particle.x = Math.random() * width;
165
+ particle.y = Math.random() * height - height;
166
+ particle.diameter = Math.random() * 10 + 5;
167
+ particle.tilt = Math.random() * 10 - 10;
168
+ particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05;
169
+ particle.tiltAngle = 0;
170
+ return particle;
171
+ }
172
+ };
173
+ __decorate([
174
+ property({ type: Number })
175
+ ], AnimateConfetti.prototype, "duration", void 0);
176
+ AnimateConfetti = __decorate([
177
+ customElement("animate-confetti")
178
+ ], AnimateConfetti);
179
+ export { AnimateConfetti };
180
+ //# sourceMappingURL=animate-confetti.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,73 @@
1
+ import { LitElement, type PropertyValues } from "lit";
2
+ /** A single autocomplete candidate: an opaque `key` plus its display `value`. */
3
+ export interface AutocompleteOption {
4
+ key: string;
5
+ value: string;
6
+ }
7
+ /**
8
+ * Generic form-associated text input with a suggestion dropdown, for any
9
+ * "type to filter a list of `{key, value}` options" use case. Works as a
10
+ * drop-in replacement for a plain `<input>` inside a `<form>`: set `name` on
11
+ * the element itself and consumers keep reading
12
+ * `new FormData(form).get(name)` and calling `form.reset()` unchanged — the
13
+ * submitted value is the picked option's `value`, while `key` is available
14
+ * via the `option-select` event and the `selectedOption` getter for cases
15
+ * that need the underlying id rather than the display text.
16
+ *
17
+ * Supports two suggestion sources:
18
+ * - **API**: set `endpoint` to a URL that, given a `?<query-param>=<text>`
19
+ * query string, responds with a JSON array of `{key, value}` objects.
20
+ * Requests are debounced as the user types.
21
+ * - **Local array**: set `options` to a fixed `AutocompleteOption[]` and the
22
+ * component filters it client-side instead of making any network
23
+ * request. Useful for small/fixed lists, offline use, or tests. Takes
24
+ * priority over `endpoint` whenever it's non-null.
25
+ *
26
+ * @element autocomplete-input
27
+ * @fires option-select - An option was picked; detail: AutocompleteOption.
28
+ */
29
+ export declare class AutocompleteInput extends LitElement {
30
+ #private;
31
+ static formAssociated: boolean;
32
+ static styles: import("lit").CSSResult[];
33
+ /** Current input value; also the form-associated value submitted with the form. */
34
+ value: string;
35
+ /** Placeholder text shown when the input is empty. */
36
+ placeholder: string;
37
+ /** Marks the input as required for native form validation. */
38
+ required: boolean;
39
+ /** API endpoint queried in API mode. Ignored when `options` is set. */
40
+ endpoint: string;
41
+ /** Query string parameter name the current input text is sent under. */
42
+ queryParam: string;
43
+ /** Debounce delay (ms) before firing a request/filter after typing. */
44
+ debounce: number;
45
+ /** Minimum query length before suggestions are shown. */
46
+ minLength: number;
47
+ /**
48
+ * Fixed candidate list to filter locally instead of fetching from
49
+ * `endpoint`. When set (non-null), no network request is ever made —
50
+ * this takes priority over the API mode.
51
+ */
52
+ options: AutocompleteOption[] | null;
53
+ private _suggestions;
54
+ private _open;
55
+ private _activeIndex;
56
+ /** Last-picked option, or null once the input diverges from it. */
57
+ get selectedOption(): AutocompleteOption | null;
58
+ protected updated(changed: PropertyValues): void;
59
+ disconnectedCallback(): void;
60
+ /** Resets to empty on `form.reset()`, per the form-associated custom element contract. */
61
+ formResetCallback(): void;
62
+ private onInput;
63
+ private onKeydown;
64
+ private onBlur;
65
+ private renderSuggestions;
66
+ render(): import("lit-html").TemplateResult<1>;
67
+ }
68
+ declare global {
69
+ interface HTMLElementTagNameMap {
70
+ "autocomplete-input": AutocompleteInput;
71
+ }
72
+ }
73
+ //# sourceMappingURL=autocomplete-input.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,309 @@
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, nothing } from "lit";
8
+ import { customElement, property, state } from "lit/decorators.js";
9
+ import { tokens } from "./tokens.js";
10
+ /**
11
+ * Generic form-associated text input with a suggestion dropdown, for any
12
+ * "type to filter a list of `{key, value}` options" use case. Works as a
13
+ * drop-in replacement for a plain `<input>` inside a `<form>`: set `name` on
14
+ * the element itself and consumers keep reading
15
+ * `new FormData(form).get(name)` and calling `form.reset()` unchanged — the
16
+ * submitted value is the picked option's `value`, while `key` is available
17
+ * via the `option-select` event and the `selectedOption` getter for cases
18
+ * that need the underlying id rather than the display text.
19
+ *
20
+ * Supports two suggestion sources:
21
+ * - **API**: set `endpoint` to a URL that, given a `?<query-param>=<text>`
22
+ * query string, responds with a JSON array of `{key, value}` objects.
23
+ * Requests are debounced as the user types.
24
+ * - **Local array**: set `options` to a fixed `AutocompleteOption[]` and the
25
+ * component filters it client-side instead of making any network
26
+ * request. Useful for small/fixed lists, offline use, or tests. Takes
27
+ * priority over `endpoint` whenever it's non-null.
28
+ *
29
+ * @element autocomplete-input
30
+ * @fires option-select - An option was picked; detail: AutocompleteOption.
31
+ */
32
+ let AutocompleteInput = class AutocompleteInput extends LitElement {
33
+ constructor() {
34
+ super(...arguments);
35
+ /** Current input value; also the form-associated value submitted with the form. */
36
+ this.value = "";
37
+ /** Placeholder text shown when the input is empty. */
38
+ this.placeholder = "";
39
+ /** Marks the input as required for native form validation. */
40
+ this.required = false;
41
+ /** API endpoint queried in API mode. Ignored when `options` is set. */
42
+ this.endpoint = "";
43
+ /** Query string parameter name the current input text is sent under. */
44
+ this.queryParam = "q";
45
+ /** Debounce delay (ms) before firing a request/filter after typing. */
46
+ this.debounce = 300;
47
+ /** Minimum query length before suggestions are shown. */
48
+ this.minLength = 3;
49
+ /**
50
+ * Fixed candidate list to filter locally instead of fetching from
51
+ * `endpoint`. When set (non-null), no network request is ever made —
52
+ * this takes priority over the API mode.
53
+ */
54
+ this.options = null;
55
+ this._suggestions = [];
56
+ this._open = false;
57
+ this._activeIndex = -1;
58
+ this.#internals = this.attachInternals();
59
+ this.#lastPicked = null;
60
+ this.#debounceTimer = null;
61
+ this.#abortController = null;
62
+ }
63
+ static { this.formAssociated = true; }
64
+ static { this.styles = [
65
+ tokens,
66
+ css `
67
+ :host {
68
+ display: block;
69
+ position: relative;
70
+ }
71
+ input {
72
+ font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
73
+ font-size: var(--ui-font-size-sm, 0.75rem);
74
+ padding: 0.35rem 0.5rem;
75
+ border: 1px solid var(--ui-border, #e2e8f0);
76
+ border-radius: var(--ui-radius-sm, 0.25rem);
77
+ width: 100%;
78
+ box-sizing: border-box;
79
+ }
80
+ .suggestions {
81
+ position: absolute;
82
+ top: 100%;
83
+ left: 0;
84
+ right: 0;
85
+ z-index: 10;
86
+ max-height: 40vh;
87
+ overflow-y: auto;
88
+ margin: 2px 0 0;
89
+ padding: 4px 0;
90
+ list-style: none;
91
+ background: var(--ui-surface, #fff);
92
+ border: 1px solid var(--ui-border, #e2e8f0);
93
+ border-radius: var(--ui-radius-sm, 0.25rem);
94
+ 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
+ }
96
+ .suggestion {
97
+ padding: 0.4rem 0.6rem;
98
+ font-size: var(--ui-font-size-sm, 0.75rem);
99
+ cursor: pointer;
100
+ }
101
+ .suggestion.active,
102
+ .suggestion:hover {
103
+ background: var(--ui-surface-muted, #f8fafc);
104
+ }
105
+ `,
106
+ ]; }
107
+ #internals;
108
+ #lastPicked;
109
+ #debounceTimer;
110
+ #abortController;
111
+ /** Last-picked option, or null once the input diverges from it. */
112
+ get selectedOption() {
113
+ if (this.#lastPicked && this.#lastPicked.value === this.value)
114
+ return this.#lastPicked;
115
+ return null;
116
+ }
117
+ updated(changed) {
118
+ if (changed.has("value"))
119
+ this.#internals.setFormValue(this.value);
120
+ }
121
+ disconnectedCallback() {
122
+ super.disconnectedCallback();
123
+ if (this.#debounceTimer)
124
+ clearTimeout(this.#debounceTimer);
125
+ this.#abortController?.abort();
126
+ }
127
+ /** Resets to empty on `form.reset()`, per the form-associated custom element contract. */
128
+ formResetCallback() {
129
+ this.value = "";
130
+ this._suggestions = [];
131
+ this._open = false;
132
+ this.#lastPicked = null;
133
+ }
134
+ onInput(e) {
135
+ this.value = e.target.value;
136
+ this.#lastPicked = null;
137
+ this.#scheduleFetch(this.value);
138
+ }
139
+ #scheduleFetch(query) {
140
+ if (this.#debounceTimer)
141
+ clearTimeout(this.#debounceTimer);
142
+ if (query.trim().length < this.minLength) {
143
+ this._suggestions = [];
144
+ this._open = false;
145
+ return;
146
+ }
147
+ if (this.options) {
148
+ // Local mode: filtering is synchronous and cheap, but keep the same
149
+ // debounce as the API mode so the UX doesn't shift between sources.
150
+ this.#debounceTimer = setTimeout(() => this.#filterLocalOptions(query), this.debounce);
151
+ return;
152
+ }
153
+ this.#debounceTimer = setTimeout(() => void this.#fetchOptions(query), this.debounce);
154
+ }
155
+ /** Filters the locally-supplied `options` list for `query`, case-insensitively. */
156
+ #filterLocalOptions(query) {
157
+ const needle = query.trim().toLowerCase();
158
+ this._suggestions = (this.options ?? [])
159
+ .filter((o) => o.value.toLowerCase().includes(needle))
160
+ .slice(0, 5);
161
+ this._activeIndex = -1;
162
+ this._open = this._suggestions.length > 0;
163
+ }
164
+ /** Fetches options for `query` from `endpoint`, cancelling any request still in flight. */
165
+ async #fetchOptions(query) {
166
+ this.#abortController?.abort();
167
+ const controller = new AbortController();
168
+ this.#abortController = controller;
169
+ try {
170
+ const res = await fetch(this.#buildUrl(query), { signal: controller.signal });
171
+ if (!res.ok)
172
+ throw new Error(`autocomplete request failed: ${res.status}`);
173
+ this._suggestions = (await res.json()).slice(0, 5);
174
+ this._activeIndex = -1;
175
+ this._open = this._suggestions.length > 0;
176
+ }
177
+ catch {
178
+ // Aborted (superseded by newer keystroke) or a network failure while
179
+ // typing — fail silently and just close the list.
180
+ if (!controller.signal.aborted) {
181
+ this._suggestions = [];
182
+ this._open = false;
183
+ }
184
+ }
185
+ }
186
+ #buildUrl(query) {
187
+ const params = new URLSearchParams({ [this.queryParam]: query });
188
+ return `${this.endpoint}?${params.toString()}`;
189
+ }
190
+ onKeydown(e) {
191
+ if (e.key === "ArrowDown")
192
+ this.#moveActive(1, e);
193
+ else if (e.key === "ArrowUp")
194
+ this.#moveActive(-1, e);
195
+ else if (e.key === "Enter")
196
+ this.#handleEnter(e);
197
+ else if (e.key === "Escape")
198
+ this.#handleEscape(e);
199
+ }
200
+ #moveActive(delta, e) {
201
+ if (!this._open || this._suggestions.length === 0)
202
+ return;
203
+ e.preventDefault();
204
+ const n = this._suggestions.length;
205
+ this._activeIndex = (this._activeIndex + delta + n) % n;
206
+ }
207
+ #handleEnter(e) {
208
+ if (!this._open)
209
+ return; // closed list: let the form submit normally
210
+ e.preventDefault();
211
+ const pick = this._suggestions[this._activeIndex] ?? this._suggestions[0];
212
+ if (pick)
213
+ this.#selectOption(pick);
214
+ }
215
+ #handleEscape(e) {
216
+ if (!this._open)
217
+ return;
218
+ e.preventDefault();
219
+ this._open = false;
220
+ }
221
+ onBlur() {
222
+ this._open = false;
223
+ }
224
+ #selectOption(o) {
225
+ this.value = o.value;
226
+ this.#lastPicked = o;
227
+ this._open = false;
228
+ this._suggestions = [];
229
+ this.dispatchEvent(new CustomEvent("option-select", { detail: o, bubbles: true, composed: true }));
230
+ }
231
+ /** Selects on mousedown (not click) so it wins over the input's blur, which closes the list. */
232
+ #onSuggestionMousedown(e, o) {
233
+ e.preventDefault();
234
+ this.#selectOption(o);
235
+ }
236
+ renderSuggestions() {
237
+ if (!this._open || this._suggestions.length === 0)
238
+ return nothing;
239
+ return html `
240
+ <ul class="suggestions" role="listbox">
241
+ ${this._suggestions.map((o, i) => html `
242
+ <li
243
+ role="option"
244
+ aria-selected=${i === this._activeIndex}
245
+ class="suggestion ${i === this._activeIndex ? "active" : ""}"
246
+ @mousedown=${(e) => this.#onSuggestionMousedown(e, o)}
247
+ >
248
+ ${o.value}
249
+ </li>
250
+ `)}
251
+ </ul>
252
+ `;
253
+ }
254
+ render() {
255
+ return html `
256
+ <div role="combobox" aria-expanded=${this._open} aria-haspopup="listbox">
257
+ <input
258
+ type="text"
259
+ .value=${this.value}
260
+ placeholder=${this.placeholder}
261
+ ?required=${this.required}
262
+ autocomplete="off"
263
+ @input=${this.onInput}
264
+ @keydown=${this.onKeydown}
265
+ @blur=${this.onBlur}
266
+ />
267
+ ${this.renderSuggestions()}
268
+ </div>
269
+ `;
270
+ }
271
+ };
272
+ __decorate([
273
+ property()
274
+ ], AutocompleteInput.prototype, "value", void 0);
275
+ __decorate([
276
+ property()
277
+ ], AutocompleteInput.prototype, "placeholder", void 0);
278
+ __decorate([
279
+ property({ type: Boolean })
280
+ ], AutocompleteInput.prototype, "required", void 0);
281
+ __decorate([
282
+ property()
283
+ ], AutocompleteInput.prototype, "endpoint", void 0);
284
+ __decorate([
285
+ property({ attribute: "query-param" })
286
+ ], AutocompleteInput.prototype, "queryParam", void 0);
287
+ __decorate([
288
+ property({ type: Number })
289
+ ], AutocompleteInput.prototype, "debounce", void 0);
290
+ __decorate([
291
+ property({ type: Number, attribute: "min-length" })
292
+ ], AutocompleteInput.prototype, "minLength", void 0);
293
+ __decorate([
294
+ property({ attribute: false })
295
+ ], AutocompleteInput.prototype, "options", void 0);
296
+ __decorate([
297
+ state()
298
+ ], AutocompleteInput.prototype, "_suggestions", void 0);
299
+ __decorate([
300
+ state()
301
+ ], AutocompleteInput.prototype, "_open", void 0);
302
+ __decorate([
303
+ state()
304
+ ], AutocompleteInput.prototype, "_activeIndex", void 0);
305
+ AutocompleteInput = __decorate([
306
+ customElement("autocomplete-input")
307
+ ], AutocompleteInput);
308
+ export { AutocompleteInput };
309
+ //# sourceMappingURL=autocomplete-input.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,36 @@
1
+ import { LitElement } from "lit";
2
+ /**
3
+ * Reusable confirmation dialog: overlay + centered card with a slotted body,
4
+ * an optional error line, and Cancel/Confirm actions. Instant `display:none`
5
+ * → `display:flex` toggle (no transitions). Fires `confirm`/`cancel`
6
+ * (bubbling, composed) instead of owning any deletion logic itself —
7
+ * callers stay in charge of the request.
8
+ *
9
+ * @element confirm-dialog
10
+ * @fires confirm - User clicked the confirm button.
11
+ * @fires cancel - User clicked the cancel button.
12
+ */
13
+ export declare class ConfirmDialog extends LitElement {
14
+ static styles: import("lit").CSSResult[];
15
+ /** Whether the dialog is visible. */
16
+ open: boolean;
17
+ /** Label for the confirm button. */
18
+ confirmLabel: string;
19
+ /** Label for the cancel button. */
20
+ cancelLabel: string;
21
+ /** Danger (red) vs. primary (indigo) styling for the confirm button. */
22
+ danger: boolean;
23
+ /** Shows a spinner and disables both buttons while a request is in flight. */
24
+ busy: boolean;
25
+ /** Inline error line shown below the body, or null for none. */
26
+ error: string | null;
27
+ private _confirm;
28
+ private _cancel;
29
+ render(): import("lit-html").TemplateResult<1>;
30
+ }
31
+ declare global {
32
+ interface HTMLElementTagNameMap {
33
+ "confirm-dialog": ConfirmDialog;
34
+ }
35
+ }
36
+ //# sourceMappingURL=confirm-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm-dialog.d.ts","sourceRoot":"","sources":["../src/confirm-dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD;;;;;;;;;;GAUG;AACH,qBACa,aAAc,SAAQ,UAAU;IAC3C,OAAgB,MAAM,4BA2GpB;IAEF,qCAAqC;IACR,IAAI,UAAS;IAC1C,oCAAoC;IACM,YAAY,SAAY;IAClE,mCAAmC;IACM,WAAW,SAAY;IAChE,wEAAwE;IAC3C,MAAM,UAAQ;IAC3C,8EAA8E;IACjD,IAAI,UAAS;IAC1C,gEAAgE;IACpD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IAExC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,OAAO;IAIN,MAAM,yCAed;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,aAAa,CAAC;KACjC;CACF"}