@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.
- package/README.md +6 -0
- package/custom-elements.json +891 -22
- package/dist/address-autocomplete.d.ts +8 -0
- package/dist/address-autocomplete.d.ts.map +1 -1
- package/dist/address-autocomplete.js +165 -15
- package/dist/address-autocomplete.js.map +1 -1
- package/dist/animate-confetti.d.ts +8 -5
- package/dist/animate-confetti.d.ts.map +1 -1
- package/dist/animate-confetti.js +46 -19
- package/dist/animate-confetti.js.map +1 -1
- package/dist/autocomplete-input.d.ts +8 -0
- package/dist/autocomplete-input.d.ts.map +1 -1
- package/dist/autocomplete-input.js +165 -15
- package/dist/autocomplete-input.js.map +1 -1
- package/dist/calendar-month.d.ts.map +1 -1
- package/dist/calendar-month.js +27 -13
- package/dist/calendar-month.js.map +1 -1
- package/dist/calendar-year.d.ts.map +1 -1
- package/dist/calendar-year.js +11 -2
- package/dist/calendar-year.js.map +1 -1
- package/dist/chat-message.d.ts.map +1 -1
- package/dist/chat-message.js +77 -24
- package/dist/chat-message.js.map +1 -1
- package/dist/confirm-dialog.d.ts +8 -1
- package/dist/confirm-dialog.d.ts.map +1 -1
- package/dist/confirm-dialog.js +130 -18
- package/dist/confirm-dialog.js.map +1 -1
- package/dist/copy-link-button.d.ts +2 -0
- package/dist/copy-link-button.d.ts.map +1 -1
- package/dist/copy-link-button.js +35 -3
- package/dist/copy-link-button.js.map +1 -1
- package/dist/data-table.d.ts +2 -0
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +83 -11
- package/dist/data-table.js.map +1 -1
- package/dist/distance-value.d.ts +0 -1
- package/dist/distance-value.d.ts.map +1 -1
- package/dist/distance-value.js +3 -8
- package/dist/distance-value.js.map +1 -1
- package/dist/distribution-chart.d.ts.map +1 -1
- package/dist/distribution-chart.js +54 -17
- package/dist/distribution-chart.js.map +1 -1
- package/dist/dropdown-button.d.ts.map +1 -1
- package/dist/dropdown-button.js +60 -11
- package/dist/dropdown-button.js.map +1 -1
- package/dist/editable-text.d.ts.map +1 -1
- package/dist/editable-text.js +46 -13
- package/dist/editable-text.js.map +1 -1
- package/dist/form-select.d.ts.map +1 -1
- package/dist/form-select.js +70 -22
- package/dist/form-select.js.map +1 -1
- package/dist/frame-box.d.ts.map +1 -1
- package/dist/frame-box.js +20 -12
- package/dist/frame-box.js.map +1 -1
- package/dist/icon-button.d.ts.map +1 -1
- package/dist/icon-button.js +11 -0
- package/dist/icon-button.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/kbd-hint.d.ts +30 -0
- package/dist/kbd-hint.d.ts.map +1 -0
- package/dist/kbd-hint.js +184 -0
- package/dist/kbd-hint.js.map +1 -0
- package/dist/live-timer.d.ts +0 -1
- package/dist/live-timer.d.ts.map +1 -1
- package/dist/live-timer.js +1 -6
- package/dist/live-timer.js.map +1 -1
- package/dist/map-circle.d.ts.map +1 -1
- package/dist/map-circle.js +16 -1
- package/dist/map-circle.js.map +1 -1
- package/dist/map-pin.d.ts.map +1 -1
- package/dist/map-pin.js +16 -1
- package/dist/map-pin.js.map +1 -1
- package/dist/percent-bar-chart.d.ts +3 -0
- package/dist/percent-bar-chart.d.ts.map +1 -1
- package/dist/percent-bar-chart.js +60 -4
- package/dist/percent-bar-chart.js.map +1 -1
- package/dist/photo-gallery.d.ts.map +1 -1
- package/dist/photo-gallery.js +12 -3
- package/dist/photo-gallery.js.map +1 -1
- package/dist/popover-panel.d.ts.map +1 -1
- package/dist/popover-panel.js +132 -23
- package/dist/popover-panel.js.map +1 -1
- package/dist/price-history-chart.d.ts.map +1 -1
- package/dist/price-history-chart.js +52 -18
- package/dist/price-history-chart.js.map +1 -1
- package/dist/radio-cards.d.ts +2 -0
- package/dist/radio-cards.d.ts.map +1 -1
- package/dist/radio-cards.js +51 -5
- package/dist/radio-cards.js.map +1 -1
- package/dist/radio-pills.d.ts +2 -0
- package/dist/radio-pills.d.ts.map +1 -1
- package/dist/radio-pills.js +46 -4
- package/dist/radio-pills.js.map +1 -1
- package/dist/relative-time.d.ts +0 -1
- package/dist/relative-time.d.ts.map +1 -1
- package/dist/relative-time.js +1 -6
- package/dist/relative-time.js.map +1 -1
- package/dist/reveal-button.d.ts +2 -0
- package/dist/reveal-button.d.ts.map +1 -1
- package/dist/reveal-button.js +42 -5
- package/dist/reveal-button.js.map +1 -1
- package/dist/roman-numeral.js +1 -1
- package/dist/roman-numeral.js.map +1 -1
- package/dist/slide-panel.d.ts +7 -3
- package/dist/slide-panel.d.ts.map +1 -1
- package/dist/slide-panel.js +95 -23
- package/dist/slide-panel.js.map +1 -1
- package/dist/stat-meter.d.ts.map +1 -1
- package/dist/stat-meter.js +17 -7
- package/dist/stat-meter.js.map +1 -1
- package/dist/status-pill.d.ts.map +1 -1
- package/dist/status-pill.js +26 -12
- package/dist/status-pill.js.map +1 -1
- package/dist/tile-grid.d.ts +8 -0
- package/dist/tile-grid.d.ts.map +1 -1
- package/dist/tile-grid.js +88 -12
- package/dist/tile-grid.js.map +1 -1
- package/dist/toast-notification.d.ts.map +1 -1
- package/dist/toast-notification.js +40 -11
- package/dist/toast-notification.js.map +1 -1
- package/dist/tokens.css +19 -0
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +13 -0
- package/dist/tokens.js.map +1 -1
- package/dist/ui-button.d.ts +2 -0
- package/dist/ui-button.d.ts.map +1 -1
- package/dist/ui-button.js +58 -9
- package/dist/ui-button.js.map +1 -1
- package/dist/user-avatar.d.ts.map +1 -1
- package/dist/user-avatar.js +18 -3
- package/dist/user-avatar.js.map +1 -1
- package/dist/utils/form.d.ts +3 -0
- package/dist/utils/form.d.ts.map +1 -0
- package/dist/utils/form.js +14 -0
- package/dist/utils/form.js.map +1 -0
- package/dist/utils/layer-stack.d.ts +9 -0
- package/dist/utils/layer-stack.d.ts.map +1 -0
- package/dist/utils/layer-stack.js +35 -0
- package/dist/utils/layer-stack.js.map +1 -0
- package/dist/weight-bar-chart.d.ts.map +1 -1
- package/dist/weight-bar-chart.js +27 -8
- package/dist/weight-bar-chart.js.map +1 -1
- package/docs/address-autocomplete.md +5 -0
- package/docs/autocomplete-input.md +5 -0
- package/docs/calendar-month.md +2 -0
- package/docs/chat-message.md +4 -0
- package/docs/confirm-dialog.md +2 -0
- package/docs/copy-link-button.md +2 -0
- package/docs/data-table.md +2 -0
- package/docs/design-language.md +149 -0
- package/docs/distribution-chart.md +4 -0
- package/docs/dropdown-button.md +1 -0
- package/docs/frame-box.md +2 -0
- package/docs/kbd-hint.md +42 -0
- package/docs/percent-bar-chart.md +4 -1
- package/docs/photo-gallery.md +1 -0
- package/docs/popover-panel.md +2 -0
- package/docs/price-history-chart.md +8 -1
- package/docs/radio-cards.md +2 -0
- package/docs/radio-pills.md +2 -0
- package/docs/reveal-button.md +3 -0
- package/docs/slide-panel.md +4 -2
- package/docs/status-pill.md +3 -0
- package/docs/tile-grid.md +10 -1
- package/docs/toast-notification.md +4 -1
- package/docs/ui-button.md +2 -0
- package/docs/user-avatar.md +1 -0
- package/llms.txt +59 -34
- 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;
|
|
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(
|
|
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
|
-
|
|
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:
|
|
90
|
-
padding:
|
|
118
|
+
margin: 0.25rem 0 0;
|
|
119
|
+
padding: 0.25rem 0;
|
|
91
120
|
list-style: none;
|
|
92
|
-
background: var(--ui-surface, #
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
private
|
|
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;
|
|
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"}
|
package/dist/animate-confetti.js
CHANGED
|
@@ -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.
|
|
79
|
+
this._clearTimersAndParticles();
|
|
68
80
|
}
|
|
69
|
-
static { this.styles =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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;
|