@designcrowd/library.brand-page 5.18.2 → 5.18.3-actionable-buttons-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/src/{CreatedOnBrandCrowd-de-de-DB2oJ5vl.js → CreatedOnBrandCrowd-de-de-HPO35Atc.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-fbk0Gf2G.js → CreatedOnBrandCrowd-en-us-Cl4aaalq.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-Ch4Q9__I.js → CreatedOnBrandCrowd-es-es-B_2laZxX.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-DaHJ6OMH.js → CreatedOnBrandCrowd-fr-ca-BTauzkB5.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-DaHJ6OMH.js → CreatedOnBrandCrowd-fr-fr-BTauzkB5.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-BgF8UCqy.js → CreatedOnBrandCrowd-pt-br-B0V4UZv3.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-BgF8UCqy.js → CreatedOnBrandCrowd-pt-pt-B0V4UZv3.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-j3rcGaGp.js → CreatedOnDcom-de-de-DvX7MlWL.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-Cx4eQPRU.js → CreatedOnDcom-en-us-BnXBQ4jL.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-D-1APCL9.js → CreatedOnDcom-es-es-lKzARICU.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-DAHdBw0D.js → CreatedOnDcom-fr-ca-BXBuLMp3.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-BYAw2kXr.js → CreatedOnDcom-fr-fr-wUZo-ppX.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-CdSknjZg.js → CreatedOnDcom-pt-br-Dcpf3PTV.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-DzyZ2j80.js → CreatedOnDcom-pt-pt-BUN3ynDi.js} +1 -1
- package/lib/src/brand-page-maker/composables/ai-chat/types.d.ts +3 -0
- package/lib/src/brand-page-maker/composables/ai-chat/ui-actions.d.ts +29 -0
- package/lib/src/brand-page-maker/composables/ai-chat/ui-actions.js +32 -0
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +201 -172
- package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +1 -0
- package/lib/src/brand-page-maker/composables/useEventTracker.js +1 -0
- package/lib/src/{bundled-translations-CnareLWC.js → bundled-translations-B5Fkt3qd.js} +1 -1
- package/lib/src/{bundled-translations.de-DE-2Ys7epLO.js → bundled-translations.de-DE-DpdVePM9.js} +1 -1
- package/lib/src/bundled-translations.es-ES-BsIcrzZF.js +175 -0
- package/lib/src/bundled-translations.fr-CA-G1XCp2Qh.js +175 -0
- package/lib/src/bundled-translations.fr-FR-BdwLXOnB.js +175 -0
- package/lib/src/bundled-translations.pt-BR-Bvn5ePe7.js +175 -0
- package/lib/src/bundled-translations.pt-PT-CDtkb2k1.js +175 -0
- package/lib/src/bundles/bundled-translations.de-DE.json +2 -0
- package/lib/src/bundles/bundled-translations.es-ES.json +2 -0
- package/lib/src/bundles/bundled-translations.fr-CA.json +2 -0
- package/lib/src/bundles/bundled-translations.fr-FR.json +2 -0
- package/lib/src/bundles/bundled-translations.json +2 -0
- package/lib/src/bundles/bundled-translations.pt-BR.json +2 -0
- package/lib/src/bundles/bundled-translations.pt-PT.json +2 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +102 -19
- package/lib/src/css/library.brand-page-brandPage.min.css +102 -19
- package/lib/src/css/library.brand-page-designCom.min.css +102 -19
- package/lib/src/css/library.brand-page-preview.min.css +102 -19
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-mcvVokSM.js → lib-BP3rUcsW.js} +17328 -17189
- package/lib/src/plugins/google-maps-plugin/google-maps-plugin.d.ts +14 -2
- package/lib/src/plugins/google-maps-plugin/google-maps-plugin.js +43 -13
- package/package.json +2 -2
- package/src/brand-page-maker/components/ai-text-edit/AiActionButtons.vue +34 -7
- package/src/brand-page-maker/components/ai-text-edit/AiChatbox.vue +3 -0
- package/src/brand-page-maker/components/ai-text-edit/TextBubble.vue +7 -1
- package/src/brand-page-maker/components/ai-website-builder/AiWebsiteBuilder.vue +21 -1
- package/src/brand-page-maker/components/brandpage-settings/BrandPageSettings.vue +8 -0
- package/src/brand-page-maker/composables/ai-chat/types.ts +6 -0
- package/src/brand-page-maker/composables/ai-chat/ui-actions.test.ts +80 -0
- package/src/brand-page-maker/composables/ai-chat/ui-actions.ts +58 -0
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +305 -256
- package/src/brand-page-maker/composables/useEventTracker.ts +1 -0
- package/src/brand-page-maker/constants/i18n/constants.de-DE.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.es-ES.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.fr-CA.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.fr-FR.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.pt-BR.json +2 -0
- package/src/brand-page-maker/constants/i18n/constants.pt-PT.json +2 -0
- package/src/bundles/bundled-translations.de-DE.json +2 -0
- package/src/bundles/bundled-translations.es-ES.json +2 -0
- package/src/bundles/bundled-translations.fr-CA.json +2 -0
- package/src/bundles/bundled-translations.fr-FR.json +2 -0
- package/src/bundles/bundled-translations.json +2 -0
- package/src/bundles/bundled-translations.pt-BR.json +2 -0
- package/src/bundles/bundled-translations.pt-PT.json +2 -0
- package/src/plugins/google-maps-plugin/google-maps-plugin.ts +58 -18
- package/src/shared/content-settings/components/google-address-field/GoogleAddressField.vue +148 -15
- package/src/shared/font-family-selector/FontPicker.vue +91 -4
- package/lib/src/bundled-translations.es-ES-Cjp-G8rT.js +0 -175
- package/lib/src/bundled-translations.fr-CA-CsaW4Oae.js +0 -175
- package/lib/src/bundled-translations.fr-FR-DIT2dd8J.js +0 -175
- package/lib/src/bundled-translations.pt-BR-BamoBpDW.js +0 -175
- package/lib/src/bundled-translations.pt-PT-C9A4hY_e.js +0 -175
- package/lib/src/shared/content-settings/components/google-address-field/GoogleAddressField.mixin.d.ts +0 -54
- package/lib/src/shared/content-settings/components/google-address-field/GoogleAddressField.mixin.js +0 -67
- package/src/shared/content-settings/components/google-address-field/GoogleAddressField.mixin.ts +0 -84
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"modernDisplayName": "modern",
|
|
13
13
|
"uniqueDisplayName": "unique",
|
|
14
14
|
"allFontStylesDisplayName": "All font styles",
|
|
15
|
+
"searchFontsPlaceholder": "Search fonts",
|
|
16
|
+
"noFontsFoundMessage": "No fonts found",
|
|
15
17
|
"settingsSaveSuccessMessage": "Settings saved successfully"
|
|
16
18
|
}
|
|
17
19
|
}
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"modernDisplayName": "moderno",
|
|
13
13
|
"uniqueDisplayName": "único",
|
|
14
14
|
"allFontStylesDisplayName": "Todos os estilos de fonte",
|
|
15
|
+
"searchFontsPlaceholder": "Pesquisar fontes",
|
|
16
|
+
"noFontsFoundMessage": "Não foram encontradas fontes",
|
|
15
17
|
"settingsSaveSuccessMessage": "Definições salvas com sucesso"
|
|
16
18
|
}
|
|
17
19
|
}
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"modernDisplayName": "moderno",
|
|
13
13
|
"uniqueDisplayName": "único",
|
|
14
14
|
"allFontStylesDisplayName": "Todos os estilos de letra",
|
|
15
|
+
"searchFontsPlaceholder": "Procurar tipos de letra",
|
|
16
|
+
"noFontsFoundMessage": "Nenhum tipo de letra encontrado",
|
|
15
17
|
"settingsSaveSuccessMessage": "Definições guardadas com sucesso"
|
|
16
18
|
}
|
|
17
19
|
}
|
|
@@ -1230,6 +1230,8 @@
|
|
|
1230
1230
|
"modernDisplayName": "modern",
|
|
1231
1231
|
"uniqueDisplayName": "einzigartig",
|
|
1232
1232
|
"allFontStylesDisplayName": "Alle Schriftstile",
|
|
1233
|
+
"searchFontsPlaceholder": "Schriftarten durchsuchen",
|
|
1234
|
+
"noFontsFoundMessage": "Keine Schriftarten gefunden",
|
|
1233
1235
|
"settingsSaveSuccessMessage": "Einstellungen wurden erfolgreich gespeichert"
|
|
1234
1236
|
},
|
|
1235
1237
|
"converter": {
|
|
@@ -1230,6 +1230,8 @@
|
|
|
1230
1230
|
"modernDisplayName": "moderna",
|
|
1231
1231
|
"uniqueDisplayName": "única",
|
|
1232
1232
|
"allFontStylesDisplayName": "Todos los estilos de fuente",
|
|
1233
|
+
"searchFontsPlaceholder": "Busca fuentes",
|
|
1234
|
+
"noFontsFoundMessage": "No se encontraron fuentes",
|
|
1233
1235
|
"settingsSaveSuccessMessage": "La configuración se ha guardado correctamente"
|
|
1234
1236
|
},
|
|
1235
1237
|
"converter": {
|
|
@@ -1230,6 +1230,8 @@
|
|
|
1230
1230
|
"modernDisplayName": "Moderne",
|
|
1231
1231
|
"uniqueDisplayName": "Unique",
|
|
1232
1232
|
"allFontStylesDisplayName": "Tous les styles de police",
|
|
1233
|
+
"searchFontsPlaceholder": "Rechercher des polices",
|
|
1234
|
+
"noFontsFoundMessage": "Aucune police trouvée",
|
|
1233
1235
|
"settingsSaveSuccessMessage": "Paramètres enregistrés avec succès"
|
|
1234
1236
|
},
|
|
1235
1237
|
"converter": {
|
|
@@ -1230,6 +1230,8 @@
|
|
|
1230
1230
|
"modernDisplayName": "Moderne",
|
|
1231
1231
|
"uniqueDisplayName": "Unique",
|
|
1232
1232
|
"allFontStylesDisplayName": "Tous les styles de police",
|
|
1233
|
+
"searchFontsPlaceholder": "Rechercher des polices",
|
|
1234
|
+
"noFontsFoundMessage": "Aucune police trouvée",
|
|
1233
1235
|
"settingsSaveSuccessMessage": "Paramètres enregistrés avec succès"
|
|
1234
1236
|
},
|
|
1235
1237
|
"converter": {
|
|
@@ -1230,6 +1230,8 @@
|
|
|
1230
1230
|
"modernDisplayName": "modern",
|
|
1231
1231
|
"uniqueDisplayName": "unique",
|
|
1232
1232
|
"allFontStylesDisplayName": "All font styles",
|
|
1233
|
+
"searchFontsPlaceholder": "Search fonts",
|
|
1234
|
+
"noFontsFoundMessage": "No fonts found",
|
|
1233
1235
|
"settingsSaveSuccessMessage": "Settings saved successfully"
|
|
1234
1236
|
},
|
|
1235
1237
|
"converter": {
|
|
@@ -1230,6 +1230,8 @@
|
|
|
1230
1230
|
"modernDisplayName": "moderno",
|
|
1231
1231
|
"uniqueDisplayName": "único",
|
|
1232
1232
|
"allFontStylesDisplayName": "Todos os estilos de fonte",
|
|
1233
|
+
"searchFontsPlaceholder": "Pesquisar fontes",
|
|
1234
|
+
"noFontsFoundMessage": "Não foram encontradas fontes",
|
|
1233
1235
|
"settingsSaveSuccessMessage": "Definições salvas com sucesso"
|
|
1234
1236
|
},
|
|
1235
1237
|
"converter": {
|
|
@@ -1230,6 +1230,8 @@
|
|
|
1230
1230
|
"modernDisplayName": "moderno",
|
|
1231
1231
|
"uniqueDisplayName": "único",
|
|
1232
1232
|
"allFontStylesDisplayName": "Todos os estilos de letra",
|
|
1233
|
+
"searchFontsPlaceholder": "Procurar tipos de letra",
|
|
1234
|
+
"noFontsFoundMessage": "Nenhum tipo de letra encontrado",
|
|
1233
1235
|
"settingsSaveSuccessMessage": "Definições guardadas com sucesso"
|
|
1234
1236
|
},
|
|
1235
1237
|
"converter": {
|
|
@@ -10,7 +10,6 @@ const DELAY_MS = 100;
|
|
|
10
10
|
const MAP_MODE_PLACE = 'place';
|
|
11
11
|
const SCRIPT_TAG_NAME = 'SCRIPT';
|
|
12
12
|
|
|
13
|
-
export const PLACE_CHANGED = 'place_changed';
|
|
14
13
|
export const PLACE_ID_QUERY_PREFIX = 'place_id:';
|
|
15
14
|
|
|
16
15
|
export interface GoogleMapsPluginOptions {
|
|
@@ -24,6 +23,11 @@ export interface GetEmbedGoogleMapsOptions {
|
|
|
24
23
|
readonly locale?: string;
|
|
25
24
|
}
|
|
26
25
|
|
|
26
|
+
export interface AddressDetails {
|
|
27
|
+
readonly formattedAddress: string;
|
|
28
|
+
readonly placeId: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
27
31
|
export class GoogleMapsPlugin {
|
|
28
32
|
private initialised = false;
|
|
29
33
|
private initialising = false;
|
|
@@ -51,23 +55,30 @@ export class GoogleMapsPlugin {
|
|
|
51
55
|
|
|
52
56
|
const url = this.getGoogleScriptUrl();
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
googleMapsScript = Array.from(document.head.children).find(
|
|
58
|
+
const existingScript = Array.from(document.head.children).find(
|
|
57
59
|
(c): c is HTMLScriptElement =>
|
|
58
60
|
c.tagName.toUpperCase() === SCRIPT_TAG_NAME &&
|
|
59
61
|
c.hasAttribute('src') &&
|
|
60
62
|
c.getAttribute('src') === url,
|
|
61
63
|
);
|
|
62
64
|
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
+
if (!existingScript) {
|
|
66
|
+
document.head.appendChild(this.createGoogleMapsScriptElement(url));
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// Wait for the specific class we use. The `places` library loads eagerly
|
|
70
|
+
// via the `libraries=places` script param, so the new Places classes are
|
|
71
|
+
// on the `google.maps.places` namespace once ready. We avoid
|
|
72
|
+
// `importLibrary` (absent in some hosts that already loaded Maps).
|
|
73
|
+
//
|
|
74
|
+
// `typeof google === 'undefined'` must be checked first and on its own:
|
|
75
|
+
// `typeof` only suppresses ReferenceError for a bare identifier, so
|
|
76
|
+
// touching `google.maps` while `google` is still undeclared (before the
|
|
77
|
+
// script executes) throws "google is not defined".
|
|
78
|
+
while (
|
|
79
|
+
typeof google === 'undefined' ||
|
|
80
|
+
typeof google.maps?.places?.PlaceAutocompleteElement !== 'function'
|
|
81
|
+
) {
|
|
71
82
|
await delayInMS(DELAY_MS);
|
|
72
83
|
}
|
|
73
84
|
|
|
@@ -77,23 +88,52 @@ export class GoogleMapsPlugin {
|
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
async
|
|
81
|
-
input: HTMLInputElement,
|
|
82
|
-
options?: google.maps.places.AutocompleteOptions,
|
|
83
|
-
): Promise<google.maps.places.Autocomplete> {
|
|
91
|
+
private async getPlacesLibrary(): Promise<google.maps.PlacesLibrary> {
|
|
84
92
|
await this.initialise();
|
|
85
93
|
|
|
86
94
|
while (!this.initialised) {
|
|
87
95
|
await delayInMS(DELAY_MS);
|
|
88
96
|
}
|
|
89
97
|
|
|
90
|
-
|
|
98
|
+
return google.maps.places as unknown as google.maps.PlacesLibrary;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Creates Google's official Places Autocomplete widget (new Places API).
|
|
103
|
+
* It renders its own input + dropdown with built-in icons, formatting,
|
|
104
|
+
* keyboard/a11y support and the required "Powered by Google" attribution,
|
|
105
|
+
* and manages session tokens internally. Listen for `gmp-select` to read the
|
|
106
|
+
* chosen prediction, then pass it to {@link fetchAddressDetails}.
|
|
107
|
+
*/
|
|
108
|
+
async createPlaceAutocompleteElement(
|
|
109
|
+
options?: google.maps.places.PlaceAutocompleteElementOptions,
|
|
110
|
+
): Promise<google.maps.places.PlaceAutocompleteElement> {
|
|
111
|
+
const { PlaceAutocompleteElement } = await this.getPlacesLibrary();
|
|
112
|
+
|
|
113
|
+
return new PlaceAutocompleteElement({
|
|
114
|
+
requestedLanguage: this._options.locale,
|
|
115
|
+
...options,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async fetchAddressDetails(
|
|
120
|
+
prediction: google.maps.places.PlacePrediction,
|
|
121
|
+
): Promise<AddressDetails | undefined> {
|
|
122
|
+
const place = prediction.toPlace();
|
|
91
123
|
|
|
92
124
|
// NOTE: additional fields may incur a charge, check before adding a new field
|
|
93
125
|
// See https://developers.google.com/maps/billing-and-pricing/pricing#basic-data for more information
|
|
94
|
-
|
|
126
|
+
// The widget's session token is included automatically on this first fetch.
|
|
127
|
+
await place.fetchFields({ fields: ['formattedAddress', 'id'] });
|
|
128
|
+
|
|
129
|
+
if (!place.formattedAddress || !place.id) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
95
132
|
|
|
96
|
-
return
|
|
133
|
+
return {
|
|
134
|
+
formattedAddress: place.formattedAddress,
|
|
135
|
+
placeId: place.id,
|
|
136
|
+
};
|
|
97
137
|
}
|
|
98
138
|
|
|
99
139
|
private createGoogleMapsScriptElement(url: string): HTMLScriptElement {
|
|
@@ -109,7 +149,7 @@ export class GoogleMapsPlugin {
|
|
|
109
149
|
}
|
|
110
150
|
|
|
111
151
|
private getGoogleScriptUrl(): string {
|
|
112
|
-
const baseUrl = `https://maps.googleapis.com/maps/api/js?key=${this._options.apiKey}&libraries=places`;
|
|
152
|
+
const baseUrl = `https://maps.googleapis.com/maps/api/js?key=${this._options.apiKey}&libraries=places&loading=async`;
|
|
113
153
|
|
|
114
154
|
return this._options.locale
|
|
115
155
|
? `${baseUrl}&language=${this._options.locale}`
|
|
@@ -1,24 +1,157 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div data-component="google-address-field" class="tw-w-full">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class="tw-
|
|
7
|
-
|
|
8
|
-
<p v-if="isErrorVisible" class="tw-text-error-500 tw-text-sm tw-mt-2">
|
|
3
|
+
<div ref="containerEl" data-test-address-autocomplete />
|
|
4
|
+
<p
|
|
5
|
+
v-if="errorMessage && !isValid"
|
|
6
|
+
class="tw-text-error-500 tw-text-sm tw-mt-2"
|
|
7
|
+
>
|
|
9
8
|
{{ errorMessage }}
|
|
10
9
|
</p>
|
|
11
10
|
</div>
|
|
12
11
|
</template>
|
|
13
|
-
<script>
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import {
|
|
14
|
+
computed,
|
|
15
|
+
getCurrentInstance,
|
|
16
|
+
onBeforeUnmount,
|
|
17
|
+
onMounted,
|
|
18
|
+
ref,
|
|
19
|
+
} from 'vue';
|
|
20
|
+
import type { PropType } from 'vue';
|
|
21
|
+
import type { Address } from '@brand-page';
|
|
22
|
+
import type { ContentItemFieldSettings } from '../../models';
|
|
23
|
+
import {
|
|
24
|
+
getCurrentLocale,
|
|
25
|
+
googleAddressFieldTr,
|
|
26
|
+
} from '@src/composables/useTranslate';
|
|
27
|
+
|
|
28
|
+
const props = defineProps({
|
|
29
|
+
id: {
|
|
30
|
+
type: String,
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
33
|
+
name: {
|
|
34
|
+
type: String,
|
|
35
|
+
required: true,
|
|
36
|
+
},
|
|
37
|
+
value: {
|
|
38
|
+
type: Object as PropType<Address>,
|
|
39
|
+
required: false,
|
|
40
|
+
default: undefined,
|
|
22
41
|
},
|
|
42
|
+
settings: {
|
|
43
|
+
type: Object as PropType<ContentItemFieldSettings>,
|
|
44
|
+
required: true,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const emit = defineEmits<{
|
|
49
|
+
'update:modelValue': [
|
|
50
|
+
payload: { id: string; name: string; type: 'map'; value: Address },
|
|
51
|
+
];
|
|
52
|
+
}>();
|
|
53
|
+
|
|
54
|
+
const gmap = getCurrentInstance()!.appContext.config.globalProperties.$gmap;
|
|
55
|
+
|
|
56
|
+
const containerEl = ref<HTMLElement>();
|
|
57
|
+
const errorMessage = computed(() => props.settings?.errorMessage);
|
|
58
|
+
const isValid = computed(() => props.settings?.isValid);
|
|
59
|
+
|
|
60
|
+
let element: google.maps.places.PlaceAutocompleteElement | undefined;
|
|
61
|
+
|
|
62
|
+
const handleSelect = async (
|
|
63
|
+
prediction: google.maps.places.PlacePrediction,
|
|
64
|
+
): Promise<void> => {
|
|
65
|
+
const details = await gmap.fetchAddressDetails(prediction);
|
|
66
|
+
|
|
67
|
+
if (!details) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
emit('update:modelValue', {
|
|
72
|
+
id: props.id,
|
|
73
|
+
name: props.name,
|
|
74
|
+
type: 'map',
|
|
75
|
+
value: details,
|
|
76
|
+
});
|
|
23
77
|
};
|
|
78
|
+
|
|
79
|
+
// Typed as Event so the same reference is removable; the runtime payload is a
|
|
80
|
+
// PlacePredictionSelectEvent.
|
|
81
|
+
const onSelect = (event: Event): void => {
|
|
82
|
+
const { placePrediction } =
|
|
83
|
+
event as google.maps.places.PlacePredictionSelectEvent;
|
|
84
|
+
|
|
85
|
+
void handleSelect(placePrediction);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Same classes as the shared content-settings text input, applied to the
|
|
89
|
+
// widget's host element so it matches our other fields across all brand themes
|
|
90
|
+
// (the grayscale tokens resolve per theme — no hardcoded colours). The widget's
|
|
91
|
+
// own `:host` border is lower priority than these document-level utilities.
|
|
92
|
+
const HOST_INPUT_CLASSES = [
|
|
93
|
+
'tw-block',
|
|
94
|
+
'tw-w-full',
|
|
95
|
+
'tw-box-border',
|
|
96
|
+
'tw-bg-white',
|
|
97
|
+
'tw-text-base',
|
|
98
|
+
'tw-text-grayscale-600',
|
|
99
|
+
'tw-border',
|
|
100
|
+
'tw-border-solid',
|
|
101
|
+
'tw-border-grayscale-500',
|
|
102
|
+
'focus-within:tw-border-grayscale-600',
|
|
103
|
+
'tw-rounded',
|
|
104
|
+
'tw-px-4',
|
|
105
|
+
'tw-py-2',
|
|
106
|
+
'tw-shadow-inner',
|
|
107
|
+
'tw-transition-colors',
|
|
108
|
+
'tw-duration-300',
|
|
109
|
+
'tw-ease-out',
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
onMounted(async () => {
|
|
113
|
+
element = await gmap.createPlaceAutocompleteElement({
|
|
114
|
+
value: props.value?.formattedAddress ?? '',
|
|
115
|
+
placeholder: googleAddressFieldTr('addressPlaceholder'),
|
|
116
|
+
requestedLanguage: getCurrentLocale(),
|
|
117
|
+
// Drop the widget's built-in search icon and clear button for a plain
|
|
118
|
+
// input that matches our design system.
|
|
119
|
+
noInputIcon: true,
|
|
120
|
+
noClearButton: true,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
element.classList.add(...HOST_INPUT_CLASSES);
|
|
124
|
+
element.addEventListener('gmp-select', onSelect);
|
|
125
|
+
containerEl.value?.appendChild(element);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
onBeforeUnmount(() => {
|
|
129
|
+
element?.removeEventListener('gmp-select', onSelect);
|
|
130
|
+
});
|
|
24
131
|
</script>
|
|
132
|
+
<style>
|
|
133
|
+
/* The widget renders its own input inside a closed shadow DOM. We style the box
|
|
134
|
+
on the host via Tailwind classes (see HOST_INPUT_CLASSES) and normalise the
|
|
135
|
+
exposed parts here:
|
|
136
|
+
- force the light theme (the widget otherwise follows the OS color-scheme and
|
|
137
|
+
renders dark inside our always-light panel);
|
|
138
|
+
- make the inner input inherit the host's font/colour and drop its own
|
|
139
|
+
padding (the host provides px-4/py-2);
|
|
140
|
+
- hide the widget's focus-ring so focus is shown by the host border darkening,
|
|
141
|
+
matching our other inputs. */
|
|
142
|
+
[data-component='google-address-field'] gmp-place-autocomplete {
|
|
143
|
+
color-scheme: light;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
[data-component='google-address-field'] gmp-place-autocomplete::part(input) {
|
|
147
|
+
padding: 0;
|
|
148
|
+
background: transparent;
|
|
149
|
+
color: inherit;
|
|
150
|
+
font: inherit;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
[data-component='google-address-field']
|
|
154
|
+
gmp-place-autocomplete::part(focus-ring) {
|
|
155
|
+
display: none;
|
|
156
|
+
}
|
|
157
|
+
</style>
|
|
@@ -27,6 +27,33 @@
|
|
|
27
27
|
}"
|
|
28
28
|
>
|
|
29
29
|
<div :class="{ 'tw-px-4 tw-py-3': !panelOnly }">
|
|
30
|
+
<div
|
|
31
|
+
class="tw-mb-3 tw-flex tw-h-10 tw-items-center tw-gap-2 tw-rounded-lg tw-border tw-border-solid tw-border-grayscale-500 tw-bg-white tw-px-3"
|
|
32
|
+
>
|
|
33
|
+
<Icon
|
|
34
|
+
name="search"
|
|
35
|
+
size="sm"
|
|
36
|
+
class="tw-shrink-0 tw-text-grayscale-600"
|
|
37
|
+
/>
|
|
38
|
+
<input
|
|
39
|
+
v-model="searchQuery"
|
|
40
|
+
type="text"
|
|
41
|
+
:placeholder="SEARCH_FONTS_PLACEHOLDER"
|
|
42
|
+
:aria-label="SEARCH_FONTS_PLACEHOLDER"
|
|
43
|
+
data-test-tag="font-search-input"
|
|
44
|
+
class="tw-min-w-0 tw-flex-auto tw-border-0 tw-bg-transparent tw-p-0 tw-text-base tw-text-secondary-500 tw-outline-none placeholder:tw-text-grayscale-600 placeholder:tw-opacity-50"
|
|
45
|
+
@focus="closeCategoryMenu"
|
|
46
|
+
@keydown.enter.prevent.stop="onSearchEnter"
|
|
47
|
+
@keydown.esc="onSearchEscape"
|
|
48
|
+
/>
|
|
49
|
+
<Icon
|
|
50
|
+
v-show="searchQuery"
|
|
51
|
+
name="close"
|
|
52
|
+
size="sm"
|
|
53
|
+
class="tw-shrink-0 tw-cursor-pointer tw-text-grayscale-600"
|
|
54
|
+
@click="searchQuery = ''"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
30
57
|
<Dropdown
|
|
31
58
|
ref="categoryDropdownRef"
|
|
32
59
|
class="tw-w-full tw-pb-3"
|
|
@@ -50,9 +77,16 @@
|
|
|
50
77
|
ref="fontListRef"
|
|
51
78
|
class="font-list tw-max-h-[340px] tw-overflow-y-auto"
|
|
52
79
|
>
|
|
80
|
+
<div
|
|
81
|
+
v-if="!filteredFontOptions.length"
|
|
82
|
+
class="tw-py-3 tw-text-center tw-text-sm tw-text-secondary-200"
|
|
83
|
+
>
|
|
84
|
+
{{ NO_FONTS_FOUND_MESSAGE }}
|
|
85
|
+
</div>
|
|
53
86
|
<DropdownItem
|
|
54
87
|
v-for="option in filteredFontOptions"
|
|
55
88
|
:key="option.key"
|
|
89
|
+
class="font-list-item"
|
|
56
90
|
:data-test-font="option.key"
|
|
57
91
|
@click="onFontClick(option)"
|
|
58
92
|
>
|
|
@@ -116,8 +150,11 @@ const emit = defineEmits<{
|
|
|
116
150
|
const categoryDropdownRef = ref<InstanceType<typeof Dropdown> | null>(null);
|
|
117
151
|
const fontListRef = ref<HTMLElement | null>(null);
|
|
118
152
|
const isPanelOpen = ref(false);
|
|
153
|
+
const searchQuery = ref('');
|
|
119
154
|
|
|
120
155
|
const ALL_FONT_STYLES_LABEL = constantsTr('allFontStylesDisplayName');
|
|
156
|
+
const SEARCH_FONTS_PLACEHOLDER = constantsTr('searchFontsPlaceholder');
|
|
157
|
+
const NO_FONTS_FOUND_MESSAGE = constantsTr('noFontsFoundMessage');
|
|
121
158
|
|
|
122
159
|
const fontCategories = computed(() => Object.values(FONT_FAMILY_CATEGORIES()));
|
|
123
160
|
|
|
@@ -147,11 +184,26 @@ const sortedFontOptions = computed(() =>
|
|
|
147
184
|
),
|
|
148
185
|
);
|
|
149
186
|
|
|
187
|
+
// Normalize each font name once (query-independent) so the per-keystroke
|
|
188
|
+
// filter only does a cheap substring check.
|
|
189
|
+
const searchableFontOptions = computed(() =>
|
|
190
|
+
sortedFontOptions.value.map((option) => ({
|
|
191
|
+
option,
|
|
192
|
+
searchName: stripQuotes(FONT_FAMILY_CONFIGS[option.key].name).toLowerCase(),
|
|
193
|
+
})),
|
|
194
|
+
);
|
|
195
|
+
|
|
150
196
|
const filteredFontOptions = computed(() => {
|
|
151
|
-
|
|
152
|
-
return
|
|
153
|
-
|
|
154
|
-
|
|
197
|
+
const query = searchQuery.value.trim().toLowerCase();
|
|
198
|
+
return searchableFontOptions.value
|
|
199
|
+
.filter(({ option, searchName }) => {
|
|
200
|
+
const matchesCategory =
|
|
201
|
+
!selectedCategory.value ||
|
|
202
|
+
option.categories.includes(selectedCategory.value);
|
|
203
|
+
const matchesQuery = !query || searchName.includes(query);
|
|
204
|
+
return matchesCategory && matchesQuery;
|
|
205
|
+
})
|
|
206
|
+
.map(({ option }) => option);
|
|
155
207
|
});
|
|
156
208
|
|
|
157
209
|
function scrollToSelectedFont(): void {
|
|
@@ -174,6 +226,7 @@ function scrollToSelectedFont(): void {
|
|
|
174
226
|
watch(isPanelOpen, (open) => {
|
|
175
227
|
if (open) {
|
|
176
228
|
selectedCategory.value = null;
|
|
229
|
+
searchQuery.value = '';
|
|
177
230
|
scrollToSelectedFont();
|
|
178
231
|
}
|
|
179
232
|
});
|
|
@@ -183,6 +236,7 @@ watch(fontListRef, (list, _old, onCleanup) => {
|
|
|
183
236
|
const observer = new IntersectionObserver((entries) => {
|
|
184
237
|
if (entries[0].isIntersecting) {
|
|
185
238
|
selectedCategory.value = null;
|
|
239
|
+
searchQuery.value = '';
|
|
186
240
|
scrollToSelectedFont();
|
|
187
241
|
}
|
|
188
242
|
});
|
|
@@ -204,11 +258,32 @@ function onSelectCategory(category: FontFamilyCategory | null): void {
|
|
|
204
258
|
scrollToSelectedFont();
|
|
205
259
|
}
|
|
206
260
|
|
|
261
|
+
// Close the category list when the user engages the search field (BP-5732).
|
|
262
|
+
function closeCategoryMenu(): void {
|
|
263
|
+
categoryDropdownRef.value?.hideMenu();
|
|
264
|
+
}
|
|
265
|
+
|
|
207
266
|
function onFontClick(option: StyleTextFontFamilyOption): void {
|
|
208
267
|
closePanel();
|
|
209
268
|
emit('on-select', option);
|
|
210
269
|
}
|
|
211
270
|
|
|
271
|
+
// Enter selects the first match (same path as clicking it).
|
|
272
|
+
function onSearchEnter(): void {
|
|
273
|
+
const [firstOption] = filteredFontOptions.value;
|
|
274
|
+
if (firstOption) onFontClick(firstOption);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Escape clears the query first; a second Escape closes the panel
|
|
278
|
+
// (sidebar mode — the RTE dropdown handles its own dismissal).
|
|
279
|
+
function onSearchEscape(): void {
|
|
280
|
+
if (searchQuery.value) {
|
|
281
|
+
searchQuery.value = '';
|
|
282
|
+
} else {
|
|
283
|
+
closePanel();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
212
287
|
defineExpose({ hideMenu: closePanel });
|
|
213
288
|
</script>
|
|
214
289
|
<style scoped>
|
|
@@ -220,4 +295,16 @@ defineExpose({ hideMenu: closePanel });
|
|
|
220
295
|
.font-list::-webkit-scrollbar {
|
|
221
296
|
display: none; /* Chrome, Safari, Opera */
|
|
222
297
|
}
|
|
298
|
+
|
|
299
|
+
/*
|
|
300
|
+
* Skip layout + paint for off-screen rows. Each row renders an inline-SVG
|
|
301
|
+
* preview; with ~130 rows this defers the bulk of the open-time cost to
|
|
302
|
+
* only the visible window. Rows are uniform height, so the intrinsic-size
|
|
303
|
+
* estimate keeps the scrollbar and scrollToSelectedFont accurate. Nodes
|
|
304
|
+
* stay in the DOM, so find-in-page / a11y / selectors are unaffected.
|
|
305
|
+
*/
|
|
306
|
+
.font-list-item {
|
|
307
|
+
content-visibility: auto;
|
|
308
|
+
contain-intrinsic-size: auto 36px;
|
|
309
|
+
}
|
|
223
310
|
</style>
|