@f-ewald/components 1.0.0 → 1.0.3

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 (172) hide show
  1. package/README.md +6 -0
  2. package/custom-elements.json +891 -22
  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-month.d.ts.map +1 -1
  16. package/dist/calendar-month.js +27 -13
  17. package/dist/calendar-month.js.map +1 -1
  18. package/dist/calendar-year.d.ts.map +1 -1
  19. package/dist/calendar-year.js +11 -2
  20. package/dist/calendar-year.js.map +1 -1
  21. package/dist/chat-message.d.ts.map +1 -1
  22. package/dist/chat-message.js +77 -24
  23. package/dist/chat-message.js.map +1 -1
  24. package/dist/confirm-dialog.d.ts +8 -1
  25. package/dist/confirm-dialog.d.ts.map +1 -1
  26. package/dist/confirm-dialog.js +130 -18
  27. package/dist/confirm-dialog.js.map +1 -1
  28. package/dist/copy-link-button.d.ts +2 -0
  29. package/dist/copy-link-button.d.ts.map +1 -1
  30. package/dist/copy-link-button.js +35 -3
  31. package/dist/copy-link-button.js.map +1 -1
  32. package/dist/data-table.d.ts +2 -0
  33. package/dist/data-table.d.ts.map +1 -1
  34. package/dist/data-table.js +83 -11
  35. package/dist/data-table.js.map +1 -1
  36. package/dist/distance-value.d.ts +0 -1
  37. package/dist/distance-value.d.ts.map +1 -1
  38. package/dist/distance-value.js +3 -8
  39. package/dist/distance-value.js.map +1 -1
  40. package/dist/distribution-chart.d.ts.map +1 -1
  41. package/dist/distribution-chart.js +54 -17
  42. package/dist/distribution-chart.js.map +1 -1
  43. package/dist/dropdown-button.d.ts.map +1 -1
  44. package/dist/dropdown-button.js +60 -11
  45. package/dist/dropdown-button.js.map +1 -1
  46. package/dist/editable-text.d.ts.map +1 -1
  47. package/dist/editable-text.js +46 -13
  48. package/dist/editable-text.js.map +1 -1
  49. package/dist/form-select.d.ts.map +1 -1
  50. package/dist/form-select.js +70 -22
  51. package/dist/form-select.js.map +1 -1
  52. package/dist/frame-box.d.ts.map +1 -1
  53. package/dist/frame-box.js +20 -12
  54. package/dist/frame-box.js.map +1 -1
  55. package/dist/icon-button.d.ts.map +1 -1
  56. package/dist/icon-button.js +11 -0
  57. package/dist/icon-button.js.map +1 -1
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/kbd-hint.d.ts +30 -0
  63. package/dist/kbd-hint.d.ts.map +1 -0
  64. package/dist/kbd-hint.js +184 -0
  65. package/dist/kbd-hint.js.map +1 -0
  66. package/dist/live-timer.d.ts +0 -1
  67. package/dist/live-timer.d.ts.map +1 -1
  68. package/dist/live-timer.js +1 -6
  69. package/dist/live-timer.js.map +1 -1
  70. package/dist/map-circle.d.ts.map +1 -1
  71. package/dist/map-circle.js +16 -1
  72. package/dist/map-circle.js.map +1 -1
  73. package/dist/map-pin.d.ts.map +1 -1
  74. package/dist/map-pin.js +16 -1
  75. package/dist/map-pin.js.map +1 -1
  76. package/dist/percent-bar-chart.d.ts +3 -0
  77. package/dist/percent-bar-chart.d.ts.map +1 -1
  78. package/dist/percent-bar-chart.js +60 -4
  79. package/dist/percent-bar-chart.js.map +1 -1
  80. package/dist/photo-gallery.d.ts.map +1 -1
  81. package/dist/photo-gallery.js +12 -3
  82. package/dist/photo-gallery.js.map +1 -1
  83. package/dist/popover-panel.d.ts.map +1 -1
  84. package/dist/popover-panel.js +132 -23
  85. package/dist/popover-panel.js.map +1 -1
  86. package/dist/price-history-chart.d.ts.map +1 -1
  87. package/dist/price-history-chart.js +52 -18
  88. package/dist/price-history-chart.js.map +1 -1
  89. package/dist/radio-cards.d.ts +2 -0
  90. package/dist/radio-cards.d.ts.map +1 -1
  91. package/dist/radio-cards.js +51 -5
  92. package/dist/radio-cards.js.map +1 -1
  93. package/dist/radio-pills.d.ts +2 -0
  94. package/dist/radio-pills.d.ts.map +1 -1
  95. package/dist/radio-pills.js +46 -4
  96. package/dist/radio-pills.js.map +1 -1
  97. package/dist/relative-time.d.ts +0 -1
  98. package/dist/relative-time.d.ts.map +1 -1
  99. package/dist/relative-time.js +1 -6
  100. package/dist/relative-time.js.map +1 -1
  101. package/dist/reveal-button.d.ts +2 -0
  102. package/dist/reveal-button.d.ts.map +1 -1
  103. package/dist/reveal-button.js +42 -5
  104. package/dist/reveal-button.js.map +1 -1
  105. package/dist/roman-numeral.js +1 -1
  106. package/dist/roman-numeral.js.map +1 -1
  107. package/dist/slide-panel.d.ts +7 -3
  108. package/dist/slide-panel.d.ts.map +1 -1
  109. package/dist/slide-panel.js +95 -23
  110. package/dist/slide-panel.js.map +1 -1
  111. package/dist/stat-meter.d.ts.map +1 -1
  112. package/dist/stat-meter.js +17 -7
  113. package/dist/stat-meter.js.map +1 -1
  114. package/dist/status-pill.d.ts.map +1 -1
  115. package/dist/status-pill.js +26 -12
  116. package/dist/status-pill.js.map +1 -1
  117. package/dist/tile-grid.d.ts +8 -0
  118. package/dist/tile-grid.d.ts.map +1 -1
  119. package/dist/tile-grid.js +88 -12
  120. package/dist/tile-grid.js.map +1 -1
  121. package/dist/toast-notification.d.ts.map +1 -1
  122. package/dist/toast-notification.js +40 -11
  123. package/dist/toast-notification.js.map +1 -1
  124. package/dist/tokens.css +19 -0
  125. package/dist/tokens.d.ts.map +1 -1
  126. package/dist/tokens.js +13 -0
  127. package/dist/tokens.js.map +1 -1
  128. package/dist/ui-button.d.ts +2 -0
  129. package/dist/ui-button.d.ts.map +1 -1
  130. package/dist/ui-button.js +58 -9
  131. package/dist/ui-button.js.map +1 -1
  132. package/dist/user-avatar.d.ts.map +1 -1
  133. package/dist/user-avatar.js +18 -3
  134. package/dist/user-avatar.js.map +1 -1
  135. package/dist/utils/form.d.ts +3 -0
  136. package/dist/utils/form.d.ts.map +1 -0
  137. package/dist/utils/form.js +14 -0
  138. package/dist/utils/form.js.map +1 -0
  139. package/dist/utils/layer-stack.d.ts +9 -0
  140. package/dist/utils/layer-stack.d.ts.map +1 -0
  141. package/dist/utils/layer-stack.js +35 -0
  142. package/dist/utils/layer-stack.js.map +1 -0
  143. package/dist/weight-bar-chart.d.ts.map +1 -1
  144. package/dist/weight-bar-chart.js +27 -8
  145. package/dist/weight-bar-chart.js.map +1 -1
  146. package/docs/address-autocomplete.md +5 -0
  147. package/docs/autocomplete-input.md +5 -0
  148. package/docs/calendar-month.md +2 -0
  149. package/docs/chat-message.md +4 -0
  150. package/docs/confirm-dialog.md +2 -0
  151. package/docs/copy-link-button.md +2 -0
  152. package/docs/data-table.md +2 -0
  153. package/docs/design-language.md +149 -0
  154. package/docs/distribution-chart.md +4 -0
  155. package/docs/dropdown-button.md +1 -0
  156. package/docs/frame-box.md +2 -0
  157. package/docs/kbd-hint.md +42 -0
  158. package/docs/percent-bar-chart.md +4 -1
  159. package/docs/photo-gallery.md +1 -0
  160. package/docs/popover-panel.md +2 -0
  161. package/docs/price-history-chart.md +8 -1
  162. package/docs/radio-cards.md +2 -0
  163. package/docs/radio-pills.md +2 -0
  164. package/docs/reveal-button.md +3 -0
  165. package/docs/slide-panel.md +4 -2
  166. package/docs/status-pill.md +3 -0
  167. package/docs/tile-grid.md +10 -1
  168. package/docs/toast-notification.md +4 -1
  169. package/docs/ui-button.md +2 -0
  170. package/docs/user-avatar.md +1 -0
  171. package/llms.txt +59 -34
  172. package/package.json +5 -1
@@ -32,6 +32,8 @@ export declare class AddressAutocomplete extends LitElement {
32
32
  placeholder: string;
33
33
  /** Marks the input as required for native form validation. */
34
34
  required: boolean;
35
+ /** Disables the input and closes its suggestion popup. */
36
+ disabled: boolean;
35
37
  /** Mapbox `types` param, e.g. "address" or "address,poi". */
36
38
  types: string;
37
39
  /** Geocoding endpoint URL. Defaults to the Mapbox Geocoding v6 forward URL. */
@@ -55,15 +57,21 @@ export declare class AddressAutocomplete extends LitElement {
55
57
  private _suggestions;
56
58
  private _open;
57
59
  private _activeIndex;
60
+ private _formDisabled;
58
61
  /** Last-picked suggestion, or null once the input diverges from it. */
59
62
  get selectedSuggestion(): AddressSuggestion | null;
63
+ protected willUpdate(changed: PropertyValues): void;
60
64
  protected updated(changed: PropertyValues): void;
61
65
  disconnectedCallback(): void;
62
66
  /** Resets to empty on `form.reset()`, per the form-associated custom element contract. */
63
67
  formResetCallback(): void;
68
+ /** Mirrors ancestor fieldset disabled state onto the native input. */
69
+ formDisabledCallback(disabled: boolean): void;
64
70
  private onInput;
65
71
  private onKeydown;
66
72
  private onBlur;
73
+ private onCompositionStart;
74
+ private onCompositionEnd;
67
75
  private renderSuggestions;
68
76
  render(): import("lit-html").TemplateResult<1>;
69
77
  }
@@ -1 +1 @@
1
- {"version":3,"file":"address-autocomplete.d.ts","sourceRoot":"","sources":["../src/address-autocomplete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAI1E,mCAAmC;AACnC,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAOD;;;;;;;;;;;;;;;;GAgBG;AACH,qBACa,mBAAoB,SAAQ,UAAU;;IACjD,MAAM,CAAC,cAAc,UAAQ;IAE7B,OAAgB,MAAM,4BA0CpB;IAEF,mFAAmF;IACvE,KAAK,SAAM;IACvB,sDAAsD;IAC1C,WAAW,SAAM;IAC7B,8DAA8D;IACjC,QAAQ,UAAS;IAC9C,6DAA6D;IACjD,KAAK,SAAa;IAC9B,+EAA+E;IACnE,QAAQ,EAAE,MAAM,CAAsD;IAClF,6DAA6D;IACpB,WAAW,SAAM;IAC1D,2EAA2E;IAC/D,IAAI,SAAM;IACtB,0DAA0D;IAC9C,SAAS,SAAM;IAC3B,wEAAwE;IAC5C,QAAQ,SAAO;IAC3C,2DAA2D;IACN,SAAS,SAAK;IACnE;;;;OAIG;IAC6B,WAAW,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAAQ;IAEtE,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAM;IAOnC,uEAAuE;IACvE,IAAI,kBAAkB,IAAI,iBAAiB,GAAG,IAAI,CAGjD;IAED,UAAmB,OAAO,CAAC,OAAO,EAAE,cAAc,QAEjD;IAEQ,oBAAoB,SAI5B;IAED,0FAA0F;IAC1F,iBAAiB,SAKhB;IAED,OAAO,CAAC,OAAO;IAmEf,OAAO,CAAC,SAAS;IA2BjB,OAAO,CAAC,MAAM;IAkBd,OAAO,CAAC,iBAAiB;IAoBhB,MAAM,yCAgBd;CACF;AAcD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,sBAAsB,EAAE,mBAAmB,CAAC;KAC7C;CACF"}
1
+ {"version":3,"file":"address-autocomplete.d.ts","sourceRoot":"","sources":["../src/address-autocomplete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAO1E,mCAAmC;AACnC,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAOD;;;;;;;;;;;;;;;;GAgBG;AACH,qBACa,mBAAoB,SAAQ,UAAU;;IACjD,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,6DAA6D;IACjD,KAAK,SAAa;IAC9B,+EAA+E;IACnE,QAAQ,EAAE,MAAM,CAAsD;IAClF,6DAA6D;IACpB,WAAW,SAAM;IAC1D,2EAA2E;IAC/D,IAAI,SAAM;IACtB,0DAA0D;IAC9C,SAAS,SAAM;IAC3B,wEAAwE;IAC5C,QAAQ,SAAO;IAC3C,2DAA2D;IACN,SAAS,SAAK;IACnE;;;;OAIG;IAC6B,WAAW,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAAQ;IAEtE,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAS;IAgBvC,uEAAuE;IACvE,IAAI,kBAAkB,IAAI,iBAAiB,GAAG,IAAI,CAGjD;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;IAuEf,OAAO,CAAC,SAAS;IAuCjB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,gBAAgB;IAgCxB,OAAO,CAAC,iBAAiB;IA0BhB,MAAM,yCA6Bd;CACF;AAcD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,sBAAsB,EAAE,mBAAmB,CAAC;KAC7C;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
  * Form-associated text input with a suggestion dropdown. Works as a
12
14
  * drop-in replacement for a plain `<input name="address">`: consumers keep
@@ -33,6 +35,8 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
33
35
  this.placeholder = "";
34
36
  /** Marks the input as required for native form validation. */
35
37
  this.required = false;
38
+ /** Disables the input and closes its suggestion popup. */
39
+ this.disabled = false;
36
40
  /** Mapbox `types` param, e.g. "address" or "address,poi". */
37
41
  this.types = "address";
38
42
  /** Geocoding endpoint URL. Defaults to the Mapbox Geocoding v6 forward URL. */
@@ -56,10 +60,15 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
56
60
  this._suggestions = [];
57
61
  this._open = false;
58
62
  this._activeIndex = -1;
63
+ this._formDisabled = false;
59
64
  this.#internals = this.attachInternals();
60
65
  this.#lastPicked = null;
61
66
  this.#debounceTimer = null;
62
67
  this.#abortController = null;
68
+ this.#isComposing = false;
69
+ this.#compositionJustEnded = false;
70
+ this.#compositionEndTimer = null;
71
+ this.#listboxId = `address-autocomplete-listbox-${++instanceCount}`;
63
72
  }
64
73
  static { this.formAssociated = true; }
65
74
  static { this.styles = [
@@ -70,14 +79,34 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
70
79
  position: relative;
71
80
  }
72
81
  input {
73
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
82
+ font-family: var(
83
+ --ui-font,
84
+ ui-sans-serif,
85
+ system-ui,
86
+ sans-serif,
87
+ "Apple Color Emoji",
88
+ "Segoe UI Emoji",
89
+ "Segoe UI Symbol",
90
+ "Noto Color Emoji"
91
+ );
74
92
  font-size: var(--ui-font-size-sm, 0.75rem);
75
- padding: 0.35rem 0.5rem;
93
+ color: var(--ui-text, #0f172a);
94
+ background: var(--ui-surface, #ffffff);
95
+ padding: 0.5rem;
76
96
  border: 1px solid var(--ui-border, #e2e8f0);
77
97
  border-radius: var(--ui-radius-sm, 0.25rem);
78
98
  width: 100%;
79
99
  box-sizing: border-box;
80
100
  }
101
+ input:disabled {
102
+ cursor: not-allowed;
103
+ opacity: 0.6;
104
+ }
105
+ input:focus-visible {
106
+ outline: none;
107
+ border-color: var(--ui-primary, #4f46e5);
108
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
109
+ }
81
110
  .suggestions {
82
111
  position: absolute;
83
112
  top: 100%;
@@ -86,16 +115,16 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
86
115
  z-index: 10;
87
116
  max-height: 40vh;
88
117
  overflow-y: auto;
89
- margin: 2px 0 0;
90
- padding: 4px 0;
118
+ margin: 0.25rem 0 0;
119
+ padding: 0.25rem 0;
91
120
  list-style: none;
92
- background: var(--ui-surface, #fff);
121
+ background: var(--ui-surface, #ffffff);
93
122
  border: 1px solid var(--ui-border, #e2e8f0);
94
123
  border-radius: var(--ui-radius-sm, 0.25rem);
95
124
  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));
96
125
  }
97
126
  .suggestion {
98
- padding: 0.4rem 0.6rem;
127
+ padding: 0.5rem 0.75rem;
99
128
  font-size: var(--ui-font-size-sm, 0.75rem);
100
129
  cursor: pointer;
101
130
  }
@@ -103,18 +132,58 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
103
132
  .suggestion:hover {
104
133
  background: var(--ui-surface-muted, #f8fafc);
105
134
  }
135
+ .no-suggestions {
136
+ padding: 0.5rem 0.75rem;
137
+ color: var(--ui-text-muted, #64748b);
138
+ font-size: var(--ui-font-size-sm, 0.75rem);
139
+ font-style: italic;
140
+ }
141
+ @media (forced-colors: active) {
142
+ input:focus-visible {
143
+ outline: 2px solid CanvasText;
144
+ outline-offset: 2px;
145
+ box-shadow: none;
146
+ }
147
+ input:disabled {
148
+ color: GrayText;
149
+ opacity: 1;
150
+ }
151
+ .suggestion.active,
152
+ .suggestion:hover {
153
+ color: HighlightText;
154
+ background: Highlight;
155
+ }
156
+ }
106
157
  `,
107
158
  ]; }
108
159
  #internals;
109
160
  #lastPicked;
110
161
  #debounceTimer;
111
162
  #abortController;
163
+ #isComposing;
164
+ #compositionJustEnded;
165
+ #compositionEndTimer;
166
+ #listboxId;
167
+ /** Whether the host or an ancestor fieldset currently disables the control. */
168
+ get #isDisabled() {
169
+ return this.disabled || this._formDisabled;
170
+ }
112
171
  /** Last-picked suggestion, or null once the input diverges from it. */
113
172
  get selectedSuggestion() {
114
173
  if (this.#lastPicked && this.#lastPicked.address === this.value)
115
174
  return this.#lastPicked;
116
175
  return null;
117
176
  }
177
+ willUpdate(changed) {
178
+ if (changed.has("disabled") && this.#isDisabled) {
179
+ if (this.#debounceTimer)
180
+ clearTimeout(this.#debounceTimer);
181
+ this.#resetComposition();
182
+ this.#abortController?.abort();
183
+ this._open = false;
184
+ this._activeIndex = -1;
185
+ }
186
+ }
118
187
  updated(changed) {
119
188
  if (changed.has("value"))
120
189
  this.#internals.setFormValue(this.value);
@@ -123,6 +192,7 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
123
192
  super.disconnectedCallback();
124
193
  if (this.#debounceTimer)
125
194
  clearTimeout(this.#debounceTimer);
195
+ this.#resetComposition();
126
196
  this.#abortController?.abort();
127
197
  }
128
198
  /** Resets to empty on `form.reset()`, per the form-associated custom element contract. */
@@ -132,17 +202,34 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
132
202
  this._open = false;
133
203
  this.#lastPicked = null;
134
204
  }
205
+ /** Mirrors ancestor fieldset disabled state onto the native input. */
206
+ formDisabledCallback(disabled) {
207
+ this._formDisabled = disabled;
208
+ if (!disabled)
209
+ return;
210
+ if (this.#debounceTimer)
211
+ clearTimeout(this.#debounceTimer);
212
+ this.#resetComposition();
213
+ this.#abortController?.abort();
214
+ this._open = false;
215
+ this._activeIndex = -1;
216
+ }
135
217
  onInput(e) {
218
+ if (this.#isDisabled)
219
+ return;
136
220
  this.value = e.target.value;
137
221
  this.#lastPicked = null;
138
222
  this.#scheduleFetch(this.value);
139
223
  }
140
224
  #scheduleFetch(query) {
225
+ if (this.#isDisabled)
226
+ return;
141
227
  if (this.#debounceTimer)
142
228
  clearTimeout(this.#debounceTimer);
143
229
  if (query.trim().length < this.minLength) {
144
230
  this._suggestions = [];
145
231
  this._open = false;
232
+ this._activeIndex = -1;
146
233
  return;
147
234
  }
148
235
  if (this.suggestions) {
@@ -160,7 +247,7 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
160
247
  .filter((s) => s.address.toLowerCase().includes(needle))
161
248
  .slice(0, 5);
162
249
  this._activeIndex = -1;
163
- this._open = this._suggestions.length > 0;
250
+ this._open = !this.#isDisabled;
164
251
  }
165
252
  /** Fetches suggestions for `query`, cancelling any request still in flight. */
166
253
  async #fetchSuggestions(query) {
@@ -172,9 +259,11 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
172
259
  if (!res.ok)
173
260
  throw new Error(`geocode request failed: ${res.status}`);
174
261
  const body = (await res.json());
262
+ if (this.#isDisabled)
263
+ return;
175
264
  this._suggestions = parseFeatures(body.features ?? []);
176
265
  this._activeIndex = -1;
177
- this._open = this._suggestions.length > 0;
266
+ this._open = true;
178
267
  }
179
268
  catch {
180
269
  // Aborted (superseded by newer keystroke) or a network failure while
@@ -200,6 +289,11 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
200
289
  return `${this.endpoint}?${params.toString()}`;
201
290
  }
202
291
  onKeydown(e) {
292
+ if (this.#isDisabled)
293
+ return;
294
+ if (e.isComposing || e.keyCode === 229 || this.#isComposing || this.#compositionJustEnded) {
295
+ return;
296
+ }
203
297
  if (e.key === "ArrowDown")
204
298
  this.#moveActive(1, e);
205
299
  else if (e.key === "ArrowUp")
@@ -217,12 +311,19 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
217
311
  this._activeIndex = (this._activeIndex + delta + n) % n;
218
312
  }
219
313
  #handleEnter(e) {
220
- if (!this._open)
221
- return; // closed list: let the form submit normally
222
- e.preventDefault();
223
314
  const pick = this._suggestions[this._activeIndex] ?? this._suggestions[0];
224
- if (pick)
315
+ if (this._open && pick) {
316
+ e.preventDefault();
225
317
  this.#selectSuggestion(pick);
318
+ return;
319
+ }
320
+ if (this.#internals.form) {
321
+ const form = this.#internals.form;
322
+ window.setTimeout(() => {
323
+ if (!e.defaultPrevented && form.isConnected)
324
+ submitWithDefaultButton(form);
325
+ });
326
+ }
226
327
  }
227
328
  #handleEscape(e) {
228
329
  if (!this._open)
@@ -233,6 +334,31 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
233
334
  onBlur() {
234
335
  this._open = false;
235
336
  }
337
+ onCompositionStart() {
338
+ if (this.#compositionEndTimer)
339
+ clearTimeout(this.#compositionEndTimer);
340
+ this.#compositionEndTimer = null;
341
+ this.#compositionJustEnded = false;
342
+ this.#isComposing = true;
343
+ }
344
+ onCompositionEnd() {
345
+ this.#isComposing = false;
346
+ this.#compositionJustEnded = true;
347
+ if (this.#compositionEndTimer)
348
+ clearTimeout(this.#compositionEndTimer);
349
+ this.#compositionEndTimer = setTimeout(() => {
350
+ this.#compositionJustEnded = false;
351
+ this.#compositionEndTimer = null;
352
+ });
353
+ }
354
+ /** Clears composition state when the control can no longer receive keys. */
355
+ #resetComposition() {
356
+ if (this.#compositionEndTimer)
357
+ clearTimeout(this.#compositionEndTimer);
358
+ this.#compositionEndTimer = null;
359
+ this.#isComposing = false;
360
+ this.#compositionJustEnded = false;
361
+ }
236
362
  #selectSuggestion(s) {
237
363
  this.value = s.address;
238
364
  this.#lastPicked = s;
@@ -246,12 +372,13 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
246
372
  this.#selectSuggestion(s);
247
373
  }
248
374
  renderSuggestions() {
249
- if (!this._open || this._suggestions.length === 0)
375
+ if (!this._open || this.#isDisabled)
250
376
  return nothing;
251
377
  return html `
252
- <ul class="suggestions" role="listbox">
378
+ <ul id=${this.#listboxId} class="suggestions" role="listbox" aria-label="Address suggestions">
253
379
  ${this._suggestions.map((s, i) => html `
254
380
  <li
381
+ id=${`${this.#listboxId}-option-${i}`}
255
382
  role="option"
256
383
  aria-selected=${i === this._activeIndex}
257
384
  class="suggestion ${i === this._activeIndex ? "active" : ""}"
@@ -260,19 +387,36 @@ let AddressAutocomplete = class AddressAutocomplete extends LitElement {
260
387
  ${s.address}
261
388
  </li>
262
389
  `)}
390
+ ${this._suggestions.length === 0
391
+ ? html `<li class="no-suggestions" role="presentation">
392
+ <span role="status">No suggestions found</span>
393
+ </li>`
394
+ : nothing}
263
395
  </ul>
264
396
  `;
265
397
  }
266
398
  render() {
399
+ const activeDescendant = this._open && !this.#isDisabled && this._activeIndex >= 0
400
+ ? `${this.#listboxId}-option-${this._activeIndex}`
401
+ : nothing;
402
+ const expanded = this._open && !this.#isDisabled;
267
403
  return html `
268
- <div role="combobox" aria-expanded=${this._open} aria-haspopup="listbox">
404
+ <div>
269
405
  <input
270
406
  type="text"
407
+ role="combobox"
408
+ aria-autocomplete="list"
409
+ aria-expanded=${expanded}
410
+ aria-controls=${this.#listboxId}
411
+ aria-activedescendant=${activeDescendant}
271
412
  .value=${this.value}
272
413
  placeholder=${this.placeholder}
273
414
  ?required=${this.required}
415
+ ?disabled=${this.#isDisabled}
274
416
  autocomplete="off"
275
417
  @input=${this.onInput}
418
+ @compositionstart=${this.onCompositionStart}
419
+ @compositionend=${this.onCompositionEnd}
276
420
  @keydown=${this.onKeydown}
277
421
  @blur=${this.onBlur}
278
422
  />
@@ -290,6 +434,9 @@ __decorate([
290
434
  __decorate([
291
435
  property({ type: Boolean })
292
436
  ], AddressAutocomplete.prototype, "required", void 0);
437
+ __decorate([
438
+ property({ type: Boolean, reflect: true })
439
+ ], AddressAutocomplete.prototype, "disabled", void 0);
293
440
  __decorate([
294
441
  property()
295
442
  ], AddressAutocomplete.prototype, "types", void 0);
@@ -323,6 +470,9 @@ __decorate([
323
470
  __decorate([
324
471
  state()
325
472
  ], AddressAutocomplete.prototype, "_activeIndex", void 0);
473
+ __decorate([
474
+ state()
475
+ ], AddressAutocomplete.prototype, "_formDisabled", void 0);
326
476
  AddressAutocomplete = __decorate([
327
477
  customElement("address-autocomplete")
328
478
  ], AddressAutocomplete);
@@ -1 +1 @@
1
- {"version":3,"file":"address-autocomplete.js","sourceRoot":"","sources":["../src/address-autocomplete.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 picked address suggestion. */\nexport interface AddressSuggestion {\n address: string;\n lat: number;\n lng: number;\n}\n\ninterface MapboxGeocodeFeature {\n properties?: { full_address?: string };\n geometry: { coordinates: [number, number] };\n}\n\n/**\n * Form-associated text input with a suggestion dropdown. Works as a\n * drop-in replacement for a plain `<input name=\"address\">`: consumers keep\n * reading `new FormData(form).get(\"address\")` and calling `form.reset()`\n * unchanged.\n *\n * Supports two suggestion sources:\n * - **API** (default): fetches from `endpoint`, a Mapbox Geocoding v6-shaped\n * forward-geocode URL, debounced as the user types.\n * - **Local array**: set `suggestions` to a fixed `AddressSuggestion[]` and\n * the component filters it client-side instead of making any network\n * request. Useful for small/fixed address lists, offline use, or tests.\n * Takes priority over `endpoint` whenever it's non-null.\n *\n * @element address-autocomplete\n * @fires address-select - A suggestion was picked; detail: AddressSuggestion.\n */\n@customElement(\"address-autocomplete\")\nexport class AddressAutocomplete 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 /** Mapbox `types` param, e.g. \"address\" or \"address,poi\". */\n @property() types = \"address\";\n /** Geocoding endpoint URL. Defaults to the Mapbox Geocoding v6 forward URL. */\n @property() endpoint: string = \"https://api.mapbox.com/search/geocode/v6/forward\";\n /** Mapbox access token. Required for requests to succeed. */\n @property({ attribute: \"access-token\" }) accessToken = \"\";\n /** Optional bounding box bias, comma-separated `west,south,east,north`. */\n @property() bbox = \"\";\n /** Optional proximity bias, comma-separated `lng,lat`. */\n @property() proximity = \"\";\n /** Debounce delay (ms) before firing a geocode request after typing. */\n @property({ type: Number }) debounce = 300;\n /** Minimum query length before suggestions are fetched. */\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 }) suggestions: AddressSuggestion[] | null = null;\n\n @state() private _suggestions: AddressSuggestion[] = [];\n @state() private _open = false;\n @state() private _activeIndex = -1;\n\n #internals = this.attachInternals();\n #lastPicked: AddressSuggestion | null = null;\n #debounceTimer: ReturnType<typeof setTimeout> | null = null;\n #abortController: AbortController | null = null;\n\n /** Last-picked suggestion, or null once the input diverges from it. */\n get selectedSuggestion(): AddressSuggestion | null {\n if (this.#lastPicked && this.#lastPicked.address === 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.suggestions) {\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.#filterLocalSuggestions(query), this.debounce);\n return;\n }\n this.#debounceTimer = setTimeout(() => void this.#fetchSuggestions(query), this.debounce);\n }\n\n /** Filters the locally-supplied `suggestions` list for `query`, case-insensitively. */\n #filterLocalSuggestions(query: string) {\n const needle = query.trim().toLowerCase();\n this._suggestions = (this.suggestions ?? [])\n .filter((s) => s.address.toLowerCase().includes(needle))\n .slice(0, 5);\n this._activeIndex = -1;\n this._open = this._suggestions.length > 0;\n }\n\n /** Fetches suggestions for `query`, cancelling any request still in flight. */\n async #fetchSuggestions(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(`geocode request failed: ${res.status}`);\n const body = (await res.json()) as { features?: MapboxGeocodeFeature[] };\n this._suggestions = parseFeatures(body.features ?? []);\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({\n q: query,\n access_token: this.accessToken,\n autocomplete: \"true\",\n limit: \"5\",\n types: this.types,\n });\n if (this.bbox) params.set(\"bbox\", this.bbox);\n if (this.proximity) params.set(\"proximity\", this.proximity);\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.#selectSuggestion(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 #selectSuggestion(s: AddressSuggestion) {\n this.value = s.address;\n this.#lastPicked = s;\n this._open = false;\n this._suggestions = [];\n this.dispatchEvent(new CustomEvent<AddressSuggestion>(\"address-select\", { detail: s, 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, s: AddressSuggestion) {\n e.preventDefault();\n this.#selectSuggestion(s);\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 (s, 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, s)}\n >\n ${s.address}\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\n/** Maps Mapbox v6 forward-geocode features to suggestions, skipping ones without a full_address. */\nfunction parseFeatures(features: MapboxGeocodeFeature[]): AddressSuggestion[] {\n const suggestions: AddressSuggestion[] = [];\n for (const f of features) {\n const address = f.properties?.full_address;\n if (!address) continue;\n const [lng, lat] = f.geometry.coordinates;\n suggestions.push({ address, lng, lat });\n }\n return suggestions;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"address-autocomplete\": AddressAutocomplete;\n }\n}\n"]}
1
+ {"version":3,"file":"address-autocomplete.js","sourceRoot":"","sources":["../src/address-autocomplete.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 picked address suggestion. */\nexport interface AddressSuggestion {\n address: string;\n lat: number;\n lng: number;\n}\n\ninterface MapboxGeocodeFeature {\n properties?: { full_address?: string };\n geometry: { coordinates: [number, number] };\n}\n\n/**\n * Form-associated text input with a suggestion dropdown. Works as a\n * drop-in replacement for a plain `<input name=\"address\">`: consumers keep\n * reading `new FormData(form).get(\"address\")` and calling `form.reset()`\n * unchanged.\n *\n * Supports two suggestion sources:\n * - **API** (default): fetches from `endpoint`, a Mapbox Geocoding v6-shaped\n * forward-geocode URL, debounced as the user types.\n * - **Local array**: set `suggestions` to a fixed `AddressSuggestion[]` and\n * the component filters it client-side instead of making any network\n * request. Useful for small/fixed address lists, offline use, or tests.\n * Takes priority over `endpoint` whenever it's non-null.\n *\n * @element address-autocomplete\n * @fires address-select - A suggestion was picked; detail: AddressSuggestion.\n */\n@customElement(\"address-autocomplete\")\nexport class AddressAutocomplete 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 /** Mapbox `types` param, e.g. \"address\" or \"address,poi\". */\n @property() types = \"address\";\n /** Geocoding endpoint URL. Defaults to the Mapbox Geocoding v6 forward URL. */\n @property() endpoint: string = \"https://api.mapbox.com/search/geocode/v6/forward\";\n /** Mapbox access token. Required for requests to succeed. */\n @property({ attribute: \"access-token\" }) accessToken = \"\";\n /** Optional bounding box bias, comma-separated `west,south,east,north`. */\n @property() bbox = \"\";\n /** Optional proximity bias, comma-separated `lng,lat`. */\n @property() proximity = \"\";\n /** Debounce delay (ms) before firing a geocode request after typing. */\n @property({ type: Number }) debounce = 300;\n /** Minimum query length before suggestions are fetched. */\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 }) suggestions: AddressSuggestion[] | null = null;\n\n @state() private _suggestions: AddressSuggestion[] = [];\n @state() private _open = false;\n @state() private _activeIndex = -1;\n @state() private _formDisabled = false;\n\n #internals = this.attachInternals();\n #lastPicked: AddressSuggestion | 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 = `address-autocomplete-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 suggestion, or null once the input diverges from it. */\n get selectedSuggestion(): AddressSuggestion | null {\n if (this.#lastPicked && this.#lastPicked.address === 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.suggestions) {\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.#filterLocalSuggestions(query), this.debounce);\n return;\n }\n this.#debounceTimer = setTimeout(() => void this.#fetchSuggestions(query), this.debounce);\n }\n\n /** Filters the locally-supplied `suggestions` list for `query`, case-insensitively. */\n #filterLocalSuggestions(query: string) {\n const needle = query.trim().toLowerCase();\n this._suggestions = (this.suggestions ?? [])\n .filter((s) => s.address.toLowerCase().includes(needle))\n .slice(0, 5);\n this._activeIndex = -1;\n this._open = !this.#isDisabled;\n }\n\n /** Fetches suggestions for `query`, cancelling any request still in flight. */\n async #fetchSuggestions(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(`geocode request failed: ${res.status}`);\n const body = (await res.json()) as { features?: MapboxGeocodeFeature[] };\n if (this.#isDisabled) return;\n this._suggestions = parseFeatures(body.features ?? []);\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({\n q: query,\n access_token: this.accessToken,\n autocomplete: \"true\",\n limit: \"5\",\n types: this.types,\n });\n if (this.bbox) params.set(\"bbox\", this.bbox);\n if (this.proximity) params.set(\"proximity\", this.proximity);\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.#selectSuggestion(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 #selectSuggestion(s: AddressSuggestion) {\n this.value = s.address;\n this.#lastPicked = s;\n this._open = false;\n this._suggestions = [];\n this.dispatchEvent(new CustomEvent<AddressSuggestion>(\"address-select\", { detail: s, 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, s: AddressSuggestion) {\n e.preventDefault();\n this.#selectSuggestion(s);\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=\"Address suggestions\">\n ${this._suggestions.map(\n (s, 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, s)}\n >\n ${s.address}\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\n/** Maps Mapbox v6 forward-geocode features to suggestions, skipping ones without a full_address. */\nfunction parseFeatures(features: MapboxGeocodeFeature[]): AddressSuggestion[] {\n const suggestions: AddressSuggestion[] = [];\n for (const f of features) {\n const address = f.properties?.full_address;\n if (!address) continue;\n const [lng, lat] = f.geometry.coordinates;\n suggestions.push({ address, lng, lat });\n }\n return suggestions;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"address-autocomplete\": AddressAutocomplete;\n }\n}\n"]}
@@ -15,9 +15,11 @@ export declare class AnimateConfetti extends LitElement {
15
15
  private colors;
16
16
  private waveAngle;
17
17
  private animationTimer?;
18
+ private stopTimer?;
19
+ private cleanupTimer?;
18
20
  connectedCallback(): void;
19
21
  disconnectedCallback(): void;
20
- static styles: import("lit").CSSResult;
22
+ static styles: import("lit").CSSResult[];
21
23
  /**
22
24
  * @returns The canvas element
23
25
  */
@@ -31,16 +33,17 @@ export declare class AnimateConfetti extends LitElement {
31
33
  */
32
34
  protected firstUpdated(): void;
33
35
  render(): import("lit-html").TemplateResult<1>;
36
+ /** Creates particles and starts the animation frame loop. */
34
37
  private startConfetti;
35
38
  private runAnimation;
36
39
  /**
37
40
  * Stop confetti and clean up after reasonable time.
38
41
  */
39
42
  private stopConfetti;
40
- /**
41
- * Removes any associated particle data from internal storage to regain memory.
42
- */
43
- private _cleanup;
43
+ /** Removes particle data and stops the animation frame loop. */
44
+ private readonly _cleanup;
45
+ /** Cancels all pending work when the component leaves the document. */
46
+ private _clearTimersAndParticles;
44
47
  private updateParticles;
45
48
  private drawParticles;
46
49
  private resetParticle;
@@ -1 +1 @@
1
- {"version":3,"file":"animate-confetti.d.ts","sourceRoot":"","sources":["../src/animate-confetti.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAa5C;;;;;GAKG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAC7C,4DAA4D;IAE5D,QAAQ,EAAE,MAAM,CAAQ;IAExB,OAAO,CAAC,gBAAgB,CAAO;IAC/B,OAAO,CAAC,aAAa,CAAK;IAE1B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,iBAAiB,CAAkB;IAC3C,OAAO,CAAC,MAAM,CAaZ;IACF,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,cAAc,CAAC,CAAS;IAEvB,iBAAiB,SAGzB;IAEQ,oBAAoB,SAI5B;IAED,OAAgB,MAAM,0BASpB;IAEF;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;IAED;;OAEG;IACH,OAAO,CAAC,aAAa,CAInB;IAEF;;OAEG;IACH,UAAmB,YAAY,IAAI,IAAI,CAKtC;IAEQ,MAAM,yCAEd;IAED,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,YAAY,CAUlB;IAEF;;OAEG;IACH,OAAO,CAAC,YAAY;IAKpB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,eAAe;IAyBvB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,aAAa;CAUtB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,eAAe,CAAC;KACrC;CACF"}
1
+ {"version":3,"file":"animate-confetti.d.ts","sourceRoot":"","sources":["../src/animate-confetti.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAc5C;;;;;GAKG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAC7C,4DAA4D;IAE5D,QAAQ,EAAE,MAAM,CAAQ;IAExB,OAAO,CAAC,gBAAgB,CAAO;IAC/B,OAAO,CAAC,aAAa,CAAK;IAE1B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,iBAAiB,CAAkB;IAC3C,OAAO,CAAC,MAAM,CAaZ;IACF,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,cAAc,CAAC,CAAS;IAChC,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAC,CAAS;IAErB,iBAAiB,SAGzB;IAEQ,oBAAoB,SAI5B;IAED,OAAgB,MAAM,4BAYpB;IAEF;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;IAED;;OAEG;IACH,OAAO,CAAC,aAAa,CAInB;IAEF;;OAEG;IACH,UAAmB,YAAY,IAAI,IAAI,CAStC;IAEQ,MAAM,yCAEd;IAED,6DAA6D;IAC7D,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,YAAY,CAUlB;IAEF;;OAEG;IACH,OAAO,CAAC,YAAY;IAKpB,gEAAgE;IAChE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CASvB;IAEF,uEAAuE;IACvE,OAAO,CAAC,wBAAwB;IAahC,OAAO,CAAC,eAAe;IAyBvB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,aAAa;CAUtB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,eAAe,CAAC;KACrC;CACF"}
@@ -6,6 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { LitElement, css, html } from "lit";
8
8
  import { customElement, property } from "lit/decorators.js";
9
+ import { tokens } from "./tokens.js";
9
10
  /**
10
11
  * Fullscreen confetti animation overlay, rendered on a fixed-position canvas.
11
12
  * Starts automatically on first render and stops after `duration` ms.
@@ -56,6 +57,17 @@ let AnimateConfetti = class AnimateConfetti extends LitElement {
56
57
  this.animationTimer = window.requestAnimationFrame(this.runAnimation);
57
58
  }
58
59
  };
60
+ /** Removes particle data and stops the animation frame loop. */
61
+ this._cleanup = () => {
62
+ this.particles = [];
63
+ if (this.animationTimer !== undefined) {
64
+ window.cancelAnimationFrame(this.animationTimer);
65
+ this.animationTimer = undefined;
66
+ }
67
+ const canvas = this.shadowRoot?.getElementById("confetti-canvas");
68
+ canvas?.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
69
+ this.cleanupTimer = undefined;
70
+ };
59
71
  }
60
72
  connectedCallback() {
61
73
  super.connectedCallback();
@@ -64,18 +76,21 @@ let AnimateConfetti = class AnimateConfetti extends LitElement {
64
76
  disconnectedCallback() {
65
77
  super.disconnectedCallback();
66
78
  window.removeEventListener("resize", this._handleResize);
67
- this.stopConfetti();
79
+ this._clearTimersAndParticles();
68
80
  }
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
- `; }
81
+ static { this.styles = [
82
+ tokens,
83
+ css `
84
+ #confetti-canvas {
85
+ display: block;
86
+ position: fixed;
87
+ top: 0;
88
+ left: 0;
89
+ z-index: 999999;
90
+ pointer-events: none;
91
+ }
92
+ `,
93
+ ]; }
79
94
  /**
80
95
  * @returns The canvas element
81
96
  */
@@ -86,14 +101,19 @@ let AnimateConfetti = class AnimateConfetti extends LitElement {
86
101
  * Start confetti once shadow dom has been rendered for the first time.
87
102
  */
88
103
  firstUpdated() {
104
+ this._handleResize();
105
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches)
106
+ return;
89
107
  this.startConfetti();
90
- setTimeout(() => {
108
+ this.stopTimer = window.setTimeout(() => {
109
+ this.stopTimer = undefined;
91
110
  this.stopConfetti();
92
111
  }, this.duration);
93
112
  }
94
113
  render() {
95
- return html `<canvas id="confetti-canvas"></canvas>`;
114
+ return html `<canvas id="confetti-canvas" aria-hidden="true"></canvas>`;
96
115
  }
116
+ /** Creates particles and starts the animation frame loop. */
97
117
  startConfetti() {
98
118
  const width = window.innerWidth;
99
119
  const height = window.innerHeight;
@@ -110,13 +130,20 @@ let AnimateConfetti = class AnimateConfetti extends LitElement {
110
130
  */
111
131
  stopConfetti() {
112
132
  this.streamingConfetti = false;
113
- setTimeout(this._cleanup, 5000);
133
+ this.cleanupTimer = window.setTimeout(this._cleanup, 5000);
114
134
  }
115
- /**
116
- * Removes any associated particle data from internal storage to regain memory.
117
- */
118
- _cleanup() {
119
- this.particles = [];
135
+ /** Cancels all pending work when the component leaves the document. */
136
+ _clearTimersAndParticles() {
137
+ if (this.stopTimer !== undefined) {
138
+ window.clearTimeout(this.stopTimer);
139
+ this.stopTimer = undefined;
140
+ }
141
+ if (this.cleanupTimer !== undefined) {
142
+ window.clearTimeout(this.cleanupTimer);
143
+ this.cleanupTimer = undefined;
144
+ }
145
+ this.streamingConfetti = false;
146
+ this._cleanup();
120
147
  }
121
148
  updateParticles() {
122
149
  const width = window.innerWidth;