@coras-io/embed 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +142 -0
- package/README.md +8 -7
- package/dist/components/accordion/accordion-item/accordion-item.styles.js +1 -1
- package/dist/components/alert/alert.styles.js +14 -14
- package/dist/components/attraction-card/base/attraction-card.styles.js +7 -3
- package/dist/components/attraction-discovery/attraction-discovery.styles.js +1 -1
- package/dist/components/attraction-header/attraction-header.styles.js +1 -1
- package/dist/components/attraction-option/attraction-option.styles.js +4 -4
- package/dist/components/basket/basket.styles.js +27 -26
- package/dist/components/basket-empty/basket-empty.styles.js +1 -0
- package/dist/components/basket-item/basket-item.component.js +10 -4
- package/dist/components/basket-item/basket-item.styles.js +9 -3
- package/dist/components/basket-item-ticket/basket-item-ticket.styles.js +1 -3
- package/dist/components/basket-mobile/basket-mobile.styles.js +5 -5
- package/dist/components/better-carousel/better-carousel.styles.js +2 -0
- package/dist/components/bottom-drawer/bottom-drawer.styles.js +6 -4
- package/dist/components/button/button.component.d.ts +0 -1
- package/dist/components/button/button.component.js +1 -8
- package/dist/components/button/button.constants.d.ts +1 -1
- package/dist/components/button/button.constants.js +1 -1
- package/dist/components/button/button.styles.js +66 -39
- package/dist/components/calendar/base/calendar.styles.js +3 -5
- package/dist/components/calendar/multi/multi.styles.js +7 -0
- package/dist/components/category-tag/category-tag.styles.js +10 -7
- package/dist/components/checkbox/checkbox.styles.js +2 -2
- package/dist/components/chip/chip.styles.js +2 -2
- package/dist/components/collect-fida/collect-fida.component.js +5 -3
- package/dist/components/collect-fida/collect-fida.styles.js +9 -16
- package/dist/components/crypto-auth-modal/crypto-auth-modal.component.js +6 -4
- package/dist/components/crypto-auth-modal/crypto-auth-modal.styles.js +2 -2
- package/dist/components/dialog/dialog.styles.js +10 -17
- package/dist/components/drawer/drawer.component.js +6 -0
- package/dist/components/drawer/drawer.styles.js +17 -3
- package/dist/components/dropdown/dropdown.component.js +7 -0
- package/dist/components/dropdown/dropdown.styles.js +3 -6
- package/dist/components/expiration-timer/expiration-timer.styles.js +3 -3
- package/dist/components/explore-card/explore-card.styles.js +1 -1
- package/dist/components/filter-chip/filter-chip.styles.js +11 -9
- package/dist/components/filters-drawer/filters-drawer.component.js +1 -1
- package/dist/components/filters-drawer/filters-drawer.styles.js +6 -5
- package/dist/components/image/image.styles.js +3 -3
- package/dist/components/image-chip/image-chip.styles.js +1 -1
- package/dist/components/input-field/input-field.styles.js +22 -17
- package/dist/components/itinerary/itinerary-item/itinerary-item.styles.js +1 -1
- package/dist/components/joined-fields/joined-fields.styles.js +10 -6
- package/dist/components/loading-spinner/loading-spinner.styles.js +1 -1
- package/dist/components/location-image/location-image.component.d.ts +6 -0
- package/dist/components/location-image/location-image.component.js +15 -2
- package/dist/components/location-image/location-image.styles.js +1 -1
- package/dist/components/locations-results/locations-results.styles.js +4 -6
- package/dist/components/marketing-tag/marketing-tag.styles.js +1 -1
- package/dist/components/message-card/message-card.styles.js +6 -6
- package/dist/components/option-group/option-chip/option-chip.styles.js +4 -3
- package/dist/components/order-confirmation/order-confirmation-item/order-confirmation-item.styles.js +9 -0
- package/dist/components/order-confirmation/order-confirmation.component.js +4 -3
- package/dist/components/order-confirmation/order-confirmation.styles.js +1 -1
- package/dist/components/otp/otp.styles.js +6 -6
- package/dist/components/overlay/overlay.styles.js +5 -1
- package/dist/components/pay-in/pay-in.styles.js +2 -2
- package/dist/components/payment-fiat-method/payment-fiat-method.styles.js +20 -4
- package/dist/components/payment-option/payment-option.styles.js +10 -12
- package/dist/components/performance-time/performance-time.styles.js +2 -2
- package/dist/components/phone-country-code/phone-country-code.styles.js +1 -1
- package/dist/components/points/points.styles.js +1 -1
- package/dist/components/search-button/search-button.styles.js +15 -7
- package/dist/components/seated-selector/seated-selector.component.d.ts +2 -0
- package/dist/components/seated-selector/seated-selector.component.js +53 -10
- package/dist/components/seated-selector/seated-selector.styles.js +77 -14
- package/dist/components/seats-io/seats-io.component.d.ts +2 -0
- package/dist/components/seats-io/seats-io.component.js +25 -2
- package/dist/components/seats-io/seats-io.constants.d.ts +2 -1
- package/dist/components/seats-io/seats-io.constants.js +6 -1
- package/dist/components/seats-io/seats-io.transform.d.ts +8 -2
- package/dist/components/seats-io/seats-io.transform.js +12 -4
- package/dist/components/select-list/select-list.styles.js +1 -0
- package/dist/components/select-list/select-option/select-option.styles.js +3 -3
- package/dist/components/sort-dropdown/sort-dropdown.styles.js +1 -1
- package/dist/components/sort-list/sort-list.styles.js +1 -1
- package/dist/components/sport-options/sport-option/sport-option.styles.js +2 -2
- package/dist/components/star-rating/star-rating.component.d.ts +1 -0
- package/dist/components/star-rating/star-rating.component.js +19 -23
- package/dist/components/star-rating/star-rating.styles.js +32 -6
- package/dist/components/tab/tab.styles.js +1 -1
- package/dist/components/tabs/tabs.styles.js +1 -1
- package/dist/components/tag/tag.styles.js +7 -7
- package/dist/components/text-section/text-section.styles.js +2 -1
- package/dist/components/time-tag/time-tag.styles.js +1 -2
- package/dist/components/tooltip/tooltip.styles.js +3 -3
- package/dist/components/tour-ticket-type-selector/tour-ticket-type/tour-ticket-type.styles.js +2 -2
- package/dist/components/tour-ticket-type-selector/tour-ticket-type-selector.component.js +1 -1
- package/dist/components/tour-ticket-type-selector/tour-ticket-type-selector.styles.js +1 -10
- package/dist/components/transfer-modal/transfer-modal.component.js +3 -3
- package/dist/components/transfer-modal/transfer-modal.styles.js +6 -8
- package/dist/containers/basket/basket.component.d.ts +1 -1
- package/dist/containers/basket/basket.component.js +56 -25
- package/dist/containers/footer/footer.component.d.ts +2 -0
- package/dist/containers/footer/footer.component.js +8 -0
- package/dist/containers/footer/footer.styles.js +12 -0
- package/dist/containers/navbar/navbar.component.d.ts +6 -1
- package/dist/containers/navbar/navbar.component.js +41 -9
- package/dist/containers/navbar/navbar.styles.js +47 -13
- package/dist/contexts/basket/basket.provider.d.ts +1 -0
- package/dist/contexts/basket/basket.provider.js +31 -4
- package/dist/contexts/properties.context.d.ts +4 -4
- package/dist/core/api/middlewares/booking-session/booking-session.constants.d.ts +1 -0
- package/dist/core/api/middlewares/booking-session/booking-session.constants.js +1 -0
- package/dist/core/api/middlewares/booking-session/booking-session.js +18 -27
- package/dist/core/api/middlewares/booking-session/booking-session.utils.d.ts +4 -4
- package/dist/core/api/middlewares/booking-session/booking-session.utils.js +13 -10
- package/dist/core/api/queries.js +3 -2
- package/dist/core/color-scheme.d.ts +14 -0
- package/dist/core/color-scheme.js +48 -0
- package/dist/core/currency/constants.d.ts +1 -1
- package/dist/core/currency/constants.js +4 -3
- package/dist/core/i18n/date.js +20 -9
- package/dist/core/i18n/locale-codes.d.ts +2 -2
- package/dist/core/i18n/locale-codes.js +4 -0
- package/dist/core/images/constants.d.ts +0 -5
- package/dist/core/images/constants.js +0 -5
- package/dist/core/images/helpers.d.ts +8 -0
- package/dist/core/images/helpers.js +14 -6
- package/dist/core/images/srcset.js +2 -2
- package/dist/core/storybook/demo.d.ts +4 -0
- package/dist/core/storybook/demo.js +17 -0
- package/dist/core/types/locales.d.ts +7 -0
- package/dist/core/utils/datetime.js +10 -4
- package/dist/core/utils/locale.d.ts +8 -1
- package/dist/core/utils/locale.js +10 -0
- package/dist/helpers/helpers.d.ts +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -2
- package/dist/locales/ar-SA.d.ts +279 -0
- package/dist/locales/ar-SA.js +288 -0
- package/dist/locales/da-DK.d.ts +100 -82
- package/dist/locales/da-DK.js +104 -89
- package/dist/locales/de-DE.d.ts +100 -82
- package/dist/locales/de-DE.js +104 -89
- package/dist/locales/en-IE.d.ts +250 -232
- package/dist/locales/en-IE.js +254 -239
- package/dist/locales/en-US.d.ts +100 -82
- package/dist/locales/en-US.js +107 -92
- package/dist/locales/es-ES.d.ts +100 -82
- package/dist/locales/es-ES.js +104 -89
- package/dist/locales/fr-FR.d.ts +100 -82
- package/dist/locales/fr-FR.js +104 -89
- package/dist/locales/fr-MA.d.ts +100 -82
- package/dist/locales/fr-MA.js +104 -89
- package/dist/locales/is-IS.d.ts +100 -82
- package/dist/locales/is-IS.js +104 -89
- package/dist/locales/it-IT.d.ts +100 -82
- package/dist/locales/it-IT.js +104 -89
- package/dist/locales/ms-MY.d.ts +100 -82
- package/dist/locales/ms-MY.js +106 -91
- package/dist/locales/nb-NO.d.ts +100 -82
- package/dist/locales/nb-NO.js +104 -89
- package/dist/locales/pl-PL.d.ts +100 -82
- package/dist/locales/pl-PL.js +104 -89
- package/dist/locales/pt-PT.d.ts +100 -82
- package/dist/locales/pt-PT.js +104 -89
- package/dist/locales/sv-SE.d.ts +100 -82
- package/dist/locales/sv-SE.js +104 -89
- package/dist/locales/th-TH.d.ts +100 -82
- package/dist/locales/th-TH.js +104 -89
- package/dist/locales/tr-TR.d.ts +100 -82
- package/dist/locales/tr-TR.js +104 -89
- package/dist/locales/zh-CN.d.ts +100 -82
- package/dist/locales/zh-CN.js +105 -90
- package/dist/locales/zh-HK.d.ts +279 -0
- package/dist/locales/zh-HK.js +288 -0
- package/dist/mixins/coinbase.mixin.js +1 -1
- package/dist/mixins/error-modal.styles.js +2 -2
- package/dist/mixins/location-search/location-search.js +3 -3
- package/dist/mixins/location-search/location-search.styles.js +6 -5
- package/dist/sdk/chrome.d.ts +9 -3
- package/dist/sdk/chrome.js +21 -9
- package/dist/sdk/element-adapter.js +11 -8
- package/dist/sdk/mount.d.ts +3 -3
- package/dist/sdk/mount.js +153 -48
- package/dist/sdk/navigation-intent.js +3 -0
- package/dist/sdk/page-registry.d.ts +14 -2
- package/dist/sdk/page-registry.js +33 -13
- package/dist/sdk/theme.d.ts +2 -16
- package/dist/sdk/theme.js +12 -44
- package/dist/sdk/types.d.ts +59 -30
- package/dist/sdk/url.d.ts +24 -3
- package/dist/sdk/url.js +66 -27
- package/dist/sdk/validation.d.ts +0 -7
- package/dist/sdk/validation.js +104 -48
- package/dist/styles/contrast-fixtures.d.ts +0 -5
- package/dist/styles/contrast-fixtures.js +79 -9
- package/dist/styles/contrast.js +4 -3
- package/dist/styles/defaults/component.js +17 -1
- package/dist/styles/primitives.d.ts +0 -10
- package/dist/styles/primitives.js +2 -12
- package/dist/styles/tokens.d.ts +1 -1
- package/dist/styles/tokens.generated.d.ts +1 -1
- package/dist/styles/tokens.generated.js +1 -1
- package/dist/styles/tokens.metadata.d.ts +1 -1
- package/dist/styles/tokens.metadata.js +378 -90
- package/dist/views/details-page/details-page.component.d.ts +5 -5
- package/dist/views/details-page/details-page.component.js +77 -80
- package/dist/views/details-page/details-page.styles.js +64 -9
- package/dist/views/help-page/help-page.component.d.ts +1 -0
- package/dist/views/help-page/help-page.component.js +11 -4
- package/dist/views/help-page/help-page.styles.js +21 -8
- package/dist/views/landing-page/landing-page.component.d.ts +4 -4
- package/dist/views/landing-page/landing-page.component.js +65 -65
- package/dist/views/landing-page/landing-page.styles.js +70 -3
- package/dist/views/payment-page/partials/activity-details/activity-details.styles.js +10 -33
- package/dist/views/payment-page/partials/billing-address/billing-address.styles.js +4 -9
- package/dist/views/payment-page/partials/confirmation/confirmation.styles.js +6 -6
- package/dist/views/payment-page/partials/hotel-pickup-list/hotel-pickup-list.styles.js +4 -4
- package/dist/views/payment-page/partials/payment-type-selector/payment-type-selector.styles.js +1 -0
- package/dist/views/payment-page/partials/stripe/stripe.component.d.ts +1 -1
- package/dist/views/payment-page/partials/stripe/stripe.component.js +11 -12
- package/dist/views/payment-page/partials/stripe/stripe.constants.d.ts +2 -15
- package/dist/views/payment-page/partials/stripe/stripe.constants.js +4 -17
- package/dist/views/payment-page/partials/stripe/stripe.styles.js +16 -23
- package/dist/views/payment-page/partials/stripe/stripe.utils.d.ts +25 -14
- package/dist/views/payment-page/partials/stripe/stripe.utils.js +111 -62
- package/dist/views/payment-page/partials/terms-checkbox/terms-checkbox.styles.js +1 -1
- package/dist/views/payment-page/payment-page.component.d.ts +1 -0
- package/dist/views/payment-page/payment-page.component.js +19 -9
- package/dist/views/payment-page/payment-page.styles.js +37 -32
- package/dist/views/payment-page/payment-page.utils.d.ts +7 -0
- package/dist/views/payment-page/payment-page.utils.js +29 -14
- package/dist/views/payment-page/payment-page.validators.js +9 -3
- package/dist/views/payment-page/services/payment-api.service.d.ts +1 -1
- package/dist/views/search-page/search-page.component.d.ts +1 -0
- package/dist/views/search-page/search-page.component.js +12 -6
- package/dist/views/search-page/search-page.styles.js +24 -2
- package/dist/views/suggestion-widget/suggestion-widget.styles.js +1 -1
- package/package.json +63 -76
- package/dist/custom-elements.json +0 -37397
- package/dist/sdk/meta.d.ts +0 -7
- package/dist/sdk/meta.js +0 -9
- package/dist/styles/tokens.css +0 -359
package/dist/sdk/mount.js
CHANGED
|
@@ -3,9 +3,9 @@ import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from "../views/constants.js";
|
|
|
3
3
|
import { createDiagnostics, themeHasNoColours } from "./diagnostics.js";
|
|
4
4
|
import { attachCallbackBridge, } from "./element-adapter.js";
|
|
5
5
|
import { createChrome } from "./chrome.js";
|
|
6
|
-
import { applyConfig, applyParams, captureParamDefaults, defaultPageRegistry, } from "./page-registry.js";
|
|
6
|
+
import { applyConfig, applyParams, captureConfigDefaults, captureParamDefaults, defaultPageRegistry, } from "./page-registry.js";
|
|
7
7
|
import { applyColorScheme, applyTheme, clearTheme } from "./theme.js";
|
|
8
|
-
import { safeUrl } from "@coras-io/brand-tokens";
|
|
8
|
+
import { safeUrl, migrateBrandConfig, } from "@coras-io/brand-tokens";
|
|
9
9
|
import { ensureTokensInjected } from "../styles/tokens.js";
|
|
10
10
|
import { redactValue, toPublicErrorDetail } from "./redaction.js";
|
|
11
11
|
import { assertValidPage, CorasValidationError, validateConfig, validateMountOptions, validatePageParams, } from "./validation.js";
|
|
@@ -14,6 +14,23 @@ function isThenable(value) {
|
|
|
14
14
|
value !== null &&
|
|
15
15
|
typeof value.then === "function");
|
|
16
16
|
}
|
|
17
|
+
// `apiUrl` and `distributorId` fix the mount's identity: the API client is
|
|
18
|
+
// built once around them (base.mixin), so an update cannot change them. The
|
|
19
|
+
// public type already omits them; this guards JavaScript callers.
|
|
20
|
+
const IMMUTABLE_CONFIG_KEYS = ["apiUrl", "distributorId"];
|
|
21
|
+
// Reject an update only when it would actually change an identity key, not when
|
|
22
|
+
// it merely restates the mounted value. Hosts commonly rebuild one config object
|
|
23
|
+
// and pass it to both `mount()` and `update()` (every framework wrapper does),
|
|
24
|
+
// so re-sending the unchanged `apiUrl`/`distributorId` must be a no-op rather
|
|
25
|
+
// than an error; only a genuinely different value is rejected.
|
|
26
|
+
function assertIdentityUnchanged(patch, current) {
|
|
27
|
+
const provided = patch;
|
|
28
|
+
for (const key of IMMUTABLE_CONFIG_KEYS) {
|
|
29
|
+
if (key in provided && provided[key] !== current[key]) {
|
|
30
|
+
throw new CorasValidationError("invalid_config", `config.${key} cannot be changed after mount; unmount and mount again to switch ${key === "apiUrl" ? "the API endpoint" : "distributor"}.`, `config.${key}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
17
34
|
/**
|
|
18
35
|
* Create a `mount` function bound to a page registry. The public `mount` uses
|
|
19
36
|
* the real registry; tests inject a lightweight registry so lifecycle behavior
|
|
@@ -38,11 +55,21 @@ export function createMountWith(registry) {
|
|
|
38
55
|
const root = document.createElement("div");
|
|
39
56
|
root.setAttribute("data-coras-root", "");
|
|
40
57
|
// Token system is defined on the container scope and inherits into every
|
|
41
|
-
// nested Web Component's shadow tree (plan F-004).
|
|
42
|
-
//
|
|
43
|
-
|
|
58
|
+
// nested Web Component's shadow tree (plan F-004). Adopt into the container's
|
|
59
|
+
// root node, which is its host ShadowRoot when mounted inside one - adopting
|
|
60
|
+
// into ownerDocument would leave a shadow-hosted mount with only the
|
|
61
|
+
// @property initials, losing the entire token system. `ensureTokensInjected`
|
|
62
|
+
// already accepts a ShadowRoot; give it the real root.
|
|
63
|
+
const containerRoot = container.getRootNode();
|
|
64
|
+
ensureTokensInjected(containerRoot instanceof ShadowRoot
|
|
65
|
+
? containerRoot
|
|
66
|
+
: (container.ownerDocument ?? document));
|
|
44
67
|
let currentPage = validated.page;
|
|
45
68
|
let currentConfig = validated.config;
|
|
69
|
+
const themeLogos = () => {
|
|
70
|
+
const t = migrateBrandConfig(currentConfig.theme ?? {});
|
|
71
|
+
return { logo: t.logo, logoDark: t.logoDark, navbarLogo: t.navbarLogo };
|
|
72
|
+
};
|
|
46
73
|
let currentParams = validated.params;
|
|
47
74
|
let currentChrome = options.chrome;
|
|
48
75
|
if (themeHasNoColours(currentConfig.theme))
|
|
@@ -53,8 +80,10 @@ export function createMountWith(registry) {
|
|
|
53
80
|
container.appendChild(root);
|
|
54
81
|
let destroyed = false;
|
|
55
82
|
let generation = 0;
|
|
83
|
+
let chromeGeneration = 0;
|
|
56
84
|
let pageElement = null;
|
|
57
85
|
let pageParamDefaults = {};
|
|
86
|
+
let pageConfigDefaults = {};
|
|
58
87
|
let chromeElements = [];
|
|
59
88
|
let readySettled = false;
|
|
60
89
|
let resolveReady;
|
|
@@ -93,7 +122,9 @@ export function createMountWith(registry) {
|
|
|
93
122
|
const detail = toPublicErrorDetail(error, currentPage);
|
|
94
123
|
// Per-mount logging only; the SDK never writes unconditional console output.
|
|
95
124
|
options.logger?.error?.(error);
|
|
96
|
-
|
|
125
|
+
// Deliver to `onError` via the `coras:error` event only: the callback
|
|
126
|
+
// bridge routes it to `onError`, so calling `onError` here as well would
|
|
127
|
+
// fire it twice for one error.
|
|
97
128
|
fireEvent(root, "coras:error", detail);
|
|
98
129
|
emitObservability("coras.error", { code: detail.code });
|
|
99
130
|
}
|
|
@@ -103,13 +134,32 @@ export function createMountWith(registry) {
|
|
|
103
134
|
}
|
|
104
135
|
chromeElements = [];
|
|
105
136
|
}
|
|
137
|
+
// Push `currentConfig.theme` onto the root and any live navbar. Chrome is
|
|
138
|
+
// not rebuilt on a theme-only update, so the default logo (light + dark)
|
|
139
|
+
// goes straight to the navbar.
|
|
140
|
+
function syncThemeToRoot() {
|
|
141
|
+
clearTheme(root);
|
|
142
|
+
applyTheme(root, currentConfig.theme);
|
|
143
|
+
for (const element of chromeElements) {
|
|
144
|
+
if (element.tagName === "CORAS-NAVBAR") {
|
|
145
|
+
const nav = element;
|
|
146
|
+
const logos = themeLogos();
|
|
147
|
+
nav.logo = safeUrl(logos.logo);
|
|
148
|
+
nav.logoDark = safeUrl(logos.logoDark);
|
|
149
|
+
nav.navbarLogo = safeUrl(logos.navbarLogo);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
106
153
|
async function renderPage(targetPage, generationToken) {
|
|
107
154
|
const entry = registry[targetPage];
|
|
108
155
|
await entry.load();
|
|
109
156
|
if (destroyed || generationToken !== generation)
|
|
110
157
|
return;
|
|
111
158
|
const element = document.createElement(entry.tagName);
|
|
112
|
-
applyConfig
|
|
159
|
+
// Capture element defaults before applyConfig writes to them, so a later
|
|
160
|
+
// update that clears an optional config value can restore the default.
|
|
161
|
+
const configDefaults = captureConfigDefaults(element);
|
|
162
|
+
applyConfig(element, currentConfig, configDefaults);
|
|
113
163
|
// Preview is a mount-time rendering mode, not config or a param, so it is
|
|
114
164
|
// set directly on the element; pages that honor it read their own property.
|
|
115
165
|
element.preview = preview;
|
|
@@ -118,9 +168,7 @@ export function createMountWith(registry) {
|
|
|
118
168
|
// Chrome is rebuilt per page rather than moved: the navbar/footer consume
|
|
119
169
|
// the page's context (locale, dispatch, current page…) and re-parenting a
|
|
120
170
|
// context consumer across providers leaves those bindings stale.
|
|
121
|
-
const chrome = await createChrome(currentChrome,
|
|
122
|
-
logo: currentConfig.theme?.logo,
|
|
123
|
-
});
|
|
171
|
+
const chrome = await createChrome(currentChrome, themeLogos());
|
|
124
172
|
if (destroyed || generationToken !== generation)
|
|
125
173
|
return;
|
|
126
174
|
for (const chromeElement of chrome) {
|
|
@@ -131,6 +179,7 @@ export function createMountWith(registry) {
|
|
|
131
179
|
}
|
|
132
180
|
pageElement = element;
|
|
133
181
|
pageParamDefaults = paramDefaults;
|
|
182
|
+
pageConfigDefaults = configDefaults;
|
|
134
183
|
chromeElements = chrome;
|
|
135
184
|
root.appendChild(element);
|
|
136
185
|
root.dataset.corasPage = targetPage;
|
|
@@ -138,28 +187,49 @@ export function createMountWith(registry) {
|
|
|
138
187
|
try {
|
|
139
188
|
await element.updateComplete;
|
|
140
189
|
}
|
|
141
|
-
catch {
|
|
142
|
-
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (destroyed || generationToken !== generation)
|
|
192
|
+
return;
|
|
193
|
+
// A rejected first render must not resolve `ready` as a success or
|
|
194
|
+
// emit `coras:ready`; surface the failure instead.
|
|
195
|
+
reportError(error);
|
|
196
|
+
if (!readySettled) {
|
|
197
|
+
readySettled = true;
|
|
198
|
+
rejectReady(error);
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
143
201
|
}
|
|
144
202
|
}
|
|
145
203
|
if (destroyed || generationToken !== generation)
|
|
146
204
|
return;
|
|
147
205
|
root.dataset.corasMounted = "true";
|
|
206
|
+
// `coras:ready` is the first-completed-render signal, so emit it (and
|
|
207
|
+
// resolve `ready`) exactly once; later navigations re-render without
|
|
208
|
+
// re-firing it.
|
|
148
209
|
if (!readySettled) {
|
|
149
210
|
readySettled = true;
|
|
150
211
|
resolveReady();
|
|
212
|
+
fireEvent(root, "coras:ready", { page: targetPage });
|
|
151
213
|
}
|
|
152
|
-
fireEvent(root, "coras:ready", { page: targetPage });
|
|
153
214
|
emitObservability("coras.page-loaded", { page: targetPage });
|
|
154
215
|
}
|
|
155
216
|
const bridge = attachCallbackBridge(root, options, {
|
|
156
217
|
onListenerError: (error) => options.logger?.error?.(error),
|
|
157
218
|
});
|
|
158
|
-
function startRender(targetPage) {
|
|
219
|
+
function startRender(targetPage, rollback) {
|
|
159
220
|
const token = ++generation;
|
|
221
|
+
// A fresh render owns chrome from here on; invalidate any in-flight
|
|
222
|
+
// chrome-only update so its late resolve can't append to the new page.
|
|
223
|
+
chromeGeneration += 1;
|
|
160
224
|
renderPage(targetPage, token).catch((error) => {
|
|
161
225
|
if (destroyed || token !== generation)
|
|
162
226
|
return;
|
|
227
|
+
// `renderPage` only throws before it swaps the DOM (the page chunk or
|
|
228
|
+
// the chrome never loaded), so the previous page is still on screen.
|
|
229
|
+
// Restore the state that describes it, or `app.state` would report a
|
|
230
|
+
// page that never rendered. Roll back before reporting, so the error
|
|
231
|
+
// and any state read from its handler agree.
|
|
232
|
+
rollback?.();
|
|
163
233
|
reportError(error);
|
|
164
234
|
if (!readySettled) {
|
|
165
235
|
readySettled = true;
|
|
@@ -201,61 +271,94 @@ export function createMountWith(registry) {
|
|
|
201
271
|
function applyUpdate(patch) {
|
|
202
272
|
if (destroyed)
|
|
203
273
|
return;
|
|
204
|
-
//
|
|
205
|
-
//
|
|
274
|
+
// Phase 1 - validate the whole patch before mutating any state, so a
|
|
275
|
+
// failure (unknown page, invalid params/config, immutable field) leaves
|
|
276
|
+
// the mount's state and DOM exactly as they were.
|
|
206
277
|
if (patch.page != null)
|
|
207
278
|
assertValidPage(patch.page);
|
|
279
|
+
let nextConfig = currentConfig;
|
|
208
280
|
if (patch.config) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
clearTheme(root);
|
|
212
|
-
applyTheme(root, currentConfig.theme);
|
|
213
|
-
// Chrome is not rebuilt on a theme-only update, so push the (possibly
|
|
214
|
-
// changed) default logo to the live navbar directly.
|
|
215
|
-
for (const element of chromeElements) {
|
|
216
|
-
if (element.tagName === "CORAS-NAVBAR") {
|
|
217
|
-
element.logo = safeUrl(currentConfig.theme?.logo);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// A colour-scheme flip is just the root's `color-scheme`; the
|
|
222
|
-
// `light-dark()` pairs already carry both modes.
|
|
223
|
-
if ("colorScheme" in patch.config) {
|
|
224
|
-
applyColorScheme(root, currentConfig.colorScheme);
|
|
225
|
-
}
|
|
281
|
+
assertIdentityUnchanged(patch.config, currentConfig);
|
|
282
|
+
nextConfig = validateConfig({ ...currentConfig, ...patch.config }, strict);
|
|
226
283
|
}
|
|
227
284
|
const pageChanged = patch.page != null && patch.page !== currentPage;
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
285
|
+
const targetPage = pageChanged
|
|
286
|
+
? patch.page
|
|
287
|
+
: currentPage;
|
|
288
|
+
let nextParams = currentParams;
|
|
231
289
|
if (pageChanged) {
|
|
232
|
-
|
|
233
|
-
currentParams = validatePageParams(currentPage, patch.params, strict);
|
|
234
|
-
startRender(currentPage);
|
|
235
|
-
return;
|
|
290
|
+
nextParams = validatePageParams(targetPage, patch.params, strict);
|
|
236
291
|
}
|
|
237
|
-
if (patch.params) {
|
|
292
|
+
else if (patch.params) {
|
|
238
293
|
// Params are the full desired state for the page: replace rather than
|
|
239
294
|
// merge so params the host dropped (cleared) are reset to defaults by
|
|
240
295
|
// applyParams instead of lingering.
|
|
241
|
-
|
|
296
|
+
nextParams = validatePageParams(currentPage, patch.params, strict);
|
|
297
|
+
}
|
|
298
|
+
// Phase 2 - commit. Nothing below throws on invalid input.
|
|
299
|
+
const themeChanged = patch.config != null && "theme" in patch.config;
|
|
300
|
+
const colorSchemeChanged = patch.config != null && "colorScheme" in patch.config;
|
|
301
|
+
const chromeChanged = patch.chrome !== undefined;
|
|
302
|
+
const previous = {
|
|
303
|
+
page: currentPage,
|
|
304
|
+
params: currentParams,
|
|
305
|
+
config: currentConfig,
|
|
306
|
+
chrome: currentChrome,
|
|
307
|
+
};
|
|
308
|
+
currentConfig = nextConfig;
|
|
309
|
+
currentParams = nextParams;
|
|
310
|
+
if (chromeChanged)
|
|
311
|
+
currentChrome = patch.chrome;
|
|
312
|
+
if (themeChanged)
|
|
313
|
+
syncThemeToRoot();
|
|
314
|
+
// A colour-scheme flip is just the root's `color-scheme`; the
|
|
315
|
+
// `light-dark()` pairs already carry both modes.
|
|
316
|
+
if (colorSchemeChanged) {
|
|
317
|
+
applyColorScheme(root, currentConfig.colorScheme);
|
|
318
|
+
}
|
|
319
|
+
if (pageChanged) {
|
|
320
|
+
currentPage = targetPage;
|
|
321
|
+
// A page change renders asynchronously, so the rest of the patch is not
|
|
322
|
+
// durable until it lands: the new config reaches the DOM only through
|
|
323
|
+
// the new page element. If the render fails, undo the whole patch so an
|
|
324
|
+
// `update()` stays all-or-nothing rather than half-applied.
|
|
325
|
+
startRender(currentPage, () => {
|
|
326
|
+
currentPage = previous.page;
|
|
327
|
+
currentParams = previous.params;
|
|
328
|
+
currentConfig = previous.config;
|
|
329
|
+
currentChrome = previous.chrome;
|
|
330
|
+
if (themeChanged)
|
|
331
|
+
syncThemeToRoot();
|
|
332
|
+
if (colorSchemeChanged) {
|
|
333
|
+
applyColorScheme(root, currentConfig.colorScheme);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
242
337
|
}
|
|
243
338
|
// No page change: update the existing element in place when possible.
|
|
244
339
|
if (pageElement) {
|
|
245
|
-
applyConfig(pageElement, currentConfig);
|
|
340
|
+
applyConfig(pageElement, currentConfig, pageConfigDefaults);
|
|
246
341
|
applyParams(pageElement, registry[currentPage], currentParams, pageParamDefaults);
|
|
247
342
|
if (chromeChanged) {
|
|
248
343
|
removeChrome();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
344
|
+
const chromeToken = ++chromeGeneration;
|
|
345
|
+
void createChrome(currentChrome, themeLogos())
|
|
346
|
+
.then((chrome) => {
|
|
347
|
+
// A newer chrome update or a page render has superseded this one;
|
|
348
|
+
// discard the stale chrome instead of appending it.
|
|
349
|
+
if (destroyed ||
|
|
350
|
+
!pageElement ||
|
|
351
|
+
chromeToken !== chromeGeneration) {
|
|
352
|
+
for (const chromeElement of chrome)
|
|
353
|
+
chromeElement.remove();
|
|
253
354
|
return;
|
|
355
|
+
}
|
|
254
356
|
for (const chromeElement of chrome) {
|
|
255
357
|
pageElement.appendChild(chromeElement);
|
|
256
358
|
}
|
|
257
359
|
chromeElements = chrome;
|
|
258
|
-
})
|
|
360
|
+
})
|
|
361
|
+
.catch(reportError);
|
|
259
362
|
}
|
|
260
363
|
}
|
|
261
364
|
else if (chromeChanged) {
|
|
@@ -283,6 +386,8 @@ export function createMountWith(registry) {
|
|
|
283
386
|
if (navigateOptions?.emitNavigate) {
|
|
284
387
|
// Programmatic navigation is tagged `host` so onNavigate handlers can
|
|
285
388
|
// avoid re-driving the host router (loop prevention, ADR 0014).
|
|
389
|
+
// `page` and `params` are correlated by this method's own signature;
|
|
390
|
+
// the cast only re-states that for the un-narrowed event map.
|
|
286
391
|
fireEvent(root, "coras:navigate", {
|
|
287
392
|
page,
|
|
288
393
|
params,
|
|
@@ -3,6 +3,9 @@ export function classifyPageUpdate(input, context) {
|
|
|
3
3
|
const currency = input.currency ?? context.currency;
|
|
4
4
|
const isNavigate = (input.page != null && input.page !== context.currentPage) ||
|
|
5
5
|
input.href != null;
|
|
6
|
+
// The public details correlate `page` with `params`; here the two arrive as
|
|
7
|
+
// separate values whose pairing was already established by `validatePageParams`
|
|
8
|
+
// upstream, so the cast re-states an invariant rather than assuming one.
|
|
6
9
|
if (isNavigate) {
|
|
7
10
|
return {
|
|
8
11
|
kind: "navigate",
|
|
@@ -14,8 +14,20 @@ export type PageEntry = {
|
|
|
14
14
|
};
|
|
15
15
|
export type PageRegistry = Record<CorasPageName, PageEntry>;
|
|
16
16
|
export declare const defaultPageRegistry: PageRegistry;
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Snapshot the element's default values for every property an optional config
|
|
19
|
+
* field drives. Taken right after creation (before `applyConfig`) so a later
|
|
20
|
+
* update that drops an optional value resets the property to its default - i.e.
|
|
21
|
+
* "clears" it - instead of leaving the stale value in place. Mirrors
|
|
22
|
+
* `captureParamDefaults` for params.
|
|
23
|
+
*/
|
|
24
|
+
export declare function captureConfigDefaults(element: ConfigurableElement): Record<string, unknown>;
|
|
25
|
+
/**
|
|
26
|
+
* Apply normalized SDK config to a page (or chrome) element's properties. An
|
|
27
|
+
* optional field the config omits resets its property to the captured default,
|
|
28
|
+
* so clearing `locale`/`currency`/etc. on an update actually takes effect.
|
|
29
|
+
*/
|
|
30
|
+
export declare function applyConfig(element: ConfigurableElement, config: CorasConfig, defaults: Record<string, unknown>): void;
|
|
19
31
|
/**
|
|
20
32
|
* Snapshot the element's default values for every param the page exposes. Taken
|
|
21
33
|
* right after creation (before any params are applied) so a later update can
|
|
@@ -40,22 +40,42 @@ export const defaultPageRegistry = {
|
|
|
40
40
|
paramToProperty: identity("limit", "showExploreCard", "country", "city"),
|
|
41
41
|
},
|
|
42
42
|
};
|
|
43
|
-
/**
|
|
44
|
-
|
|
43
|
+
/** Element properties driven by an optional config field, in config -> property form. */
|
|
44
|
+
const OPTIONAL_CONFIG_PROPERTIES = [
|
|
45
|
+
"locale",
|
|
46
|
+
"locales",
|
|
47
|
+
"currency",
|
|
48
|
+
"currencies",
|
|
49
|
+
"loyaltyPoints",
|
|
50
|
+
];
|
|
51
|
+
/**
|
|
52
|
+
* Snapshot the element's default values for every property an optional config
|
|
53
|
+
* field drives. Taken right after creation (before `applyConfig`) so a later
|
|
54
|
+
* update that drops an optional value resets the property to its default - i.e.
|
|
55
|
+
* "clears" it - instead of leaving the stale value in place. Mirrors
|
|
56
|
+
* `captureParamDefaults` for params.
|
|
57
|
+
*/
|
|
58
|
+
export function captureConfigDefaults(element) {
|
|
59
|
+
const defaults = {};
|
|
60
|
+
for (const property of OPTIONAL_CONFIG_PROPERTIES) {
|
|
61
|
+
defaults[property] = element[property];
|
|
62
|
+
}
|
|
63
|
+
return defaults;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Apply normalized SDK config to a page (or chrome) element's properties. An
|
|
67
|
+
* optional field the config omits resets its property to the captured default,
|
|
68
|
+
* so clearing `locale`/`currency`/etc. on an update actually takes effect.
|
|
69
|
+
*/
|
|
70
|
+
export function applyConfig(element, config, defaults) {
|
|
45
71
|
element.apiHost = config.apiUrl;
|
|
46
72
|
element.distributorID = config.distributorId;
|
|
47
73
|
element.assetsURL = config.assetsUrl;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
element.currency = config.currency;
|
|
54
|
-
if (config.allowedCurrencies != null)
|
|
55
|
-
element.currencies = config.allowedCurrencies;
|
|
56
|
-
if (config.loyaltyPointsEnabled != null) {
|
|
57
|
-
element.loyaltyPoints = config.loyaltyPointsEnabled;
|
|
58
|
-
}
|
|
74
|
+
element.locale = config.locale ?? defaults.locale;
|
|
75
|
+
element.locales = config.allowedLocales ?? defaults.locales;
|
|
76
|
+
element.currency = config.currency ?? defaults.currency;
|
|
77
|
+
element.currencies = config.allowedCurrencies ?? defaults.currencies;
|
|
78
|
+
element.loyaltyPoints = config.loyaltyPointsEnabled ?? defaults.loyaltyPoints;
|
|
59
79
|
}
|
|
60
80
|
/**
|
|
61
81
|
* Snapshot the element's default values for every param the page exposes. Taken
|
package/dist/sdk/theme.d.ts
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
import type { CorasColorScheme, CorasTheme } from "./types.js";
|
|
2
|
-
|
|
3
|
-
* The brand `theme` keys that map 1:1 to a public `--coras-brand-*` custom
|
|
4
|
-
* property. Sourced from the shared engine so the SDK, plugin, and Studio agree.
|
|
5
|
-
* `navbarShadow`, `logo`, and `fontFaces` are applied specially (below).
|
|
6
|
-
*/
|
|
7
|
-
export declare const THEME_CSS_VARS: Record<"primary" | "secondary" | "background" | "surface" | "text" | "textMuted" | "radius" | "font" | "textButton" | "bodyIcon" | "buttonRadius" | "chipRadius" | "navbar" | "navbarText" | "navbarIcon" | "navbarBorder" | "footer" | "footerText" | "footerLink" | "error" | "success" | "warning" | "info", string>;
|
|
8
|
-
/**
|
|
9
|
-
* Set the mount root's colour scheme so the token sheet's `light-dark()` pairs
|
|
10
|
-
* resolve correctly. `"system"` maps to `light dark` (follows the OS via
|
|
11
|
-
* `prefers-color-scheme`); `"inherit"` overrides the sheet's default `light` so
|
|
12
|
-
* the root inherits the host page's `color-scheme`. Cheap enough to call on its
|
|
13
|
-
* own for a mode flip.
|
|
14
|
-
*/
|
|
2
|
+
export declare const THEME_CSS_VARS: Record<"primary" | "secondary" | "pageBackground" | "text" | "textMuted" | "link" | "icon" | "focusRing" | "scrim" | "cardBackground" | "cardBorder" | "fieldBackground" | "fieldBorder" | "menuBackground" | "menuBorder" | "divider" | "cardShadow" | "softShadow" | "primaryButton" | "primaryButtonText" | "primaryButtonHover" | "primaryButtonPressed" | "secondaryButton" | "secondaryButtonText" | "secondaryButtonHover" | "secondaryButtonPressed" | "textButton" | "primaryButtonBorder" | "secondaryButtonBorder" | "disabled" | "buttonShadow" | "buttonHoverOnLightFill" | "buttonHoverOnDarkFill" | "radius" | "buttonRadius" | "chipRadius" | "fieldRadius" | "searchRadius" | "font" | "headingFont" | "headingScale" | "logoHeight" | "navbarBackground" | "navbarBackgroundImage" | "navbarText" | "navbarIcon" | "navbarBorder" | "navbarShadow" | "footerBackground" | "footerBackgroundImage" | "footerText" | "footerLink" | "footerBorder" | "footerPaddingBottom" | "error" | "errorBackground" | "success" | "successBackground" | "warning" | "warningBackground" | "info" | "infoBackground" | "primaryTint" | "secondaryTint" | "neutral50" | "neutral100" | "neutral200" | "neutral300" | "neutral400" | "neutral500" | "neutral600" | "neutral700" | "neutral800" | "neutral900", string>;
|
|
15
3
|
export declare function applyColorScheme(element: HTMLElement, scheme: CorasColorScheme | undefined): void;
|
|
16
|
-
|
|
17
|
-
export declare function applyTheme(element: HTMLElement, theme: CorasTheme | undefined): void;
|
|
18
|
-
/** Remove any brand variables/fonts previously set on an element. */
|
|
4
|
+
export declare function applyTheme(element: HTMLElement, input: CorasTheme | undefined): void;
|
|
19
5
|
export declare function clearTheme(element: HTMLElement): void;
|
package/dist/sdk/theme.js
CHANGED
|
@@ -1,56 +1,23 @@
|
|
|
1
|
-
import { BRAND_CSS_VARS, brandVarValue, fontFacesToCSS,
|
|
2
|
-
/**
|
|
3
|
-
* The brand `theme` keys that map 1:1 to a public `--coras-brand-*` custom
|
|
4
|
-
* property. Sourced from the shared engine so the SDK, plugin, and Studio agree.
|
|
5
|
-
* `navbarShadow`, `logo`, and `fontFaces` are applied specially (below).
|
|
6
|
-
*/
|
|
1
|
+
import { BRAND_CSS_VARS, BRAND_DARK_CSS_VARS, brandVarEntries, brandVarValue, fontFacesToCSS, migrateBrandConfig, } from "@coras-io/brand-tokens";
|
|
7
2
|
export const THEME_CSS_VARS = BRAND_CSS_VARS;
|
|
8
3
|
const THEME_KEYS = Object.keys(THEME_CSS_VARS);
|
|
9
|
-
/**
|
|
10
|
-
* Set the mount root's colour scheme so the token sheet's `light-dark()` pairs
|
|
11
|
-
* resolve correctly. `"system"` maps to `light dark` (follows the OS via
|
|
12
|
-
* `prefers-color-scheme`); `"inherit"` overrides the sheet's default `light` so
|
|
13
|
-
* the root inherits the host page's `color-scheme`. Cheap enough to call on its
|
|
14
|
-
* own for a mode flip.
|
|
15
|
-
*/
|
|
16
4
|
export function applyColorScheme(element, scheme) {
|
|
17
5
|
element.style.colorScheme =
|
|
18
|
-
scheme === "dark"
|
|
19
|
-
? "dark"
|
|
20
|
-
: scheme === "system"
|
|
21
|
-
? "light dark"
|
|
22
|
-
: scheme === "inherit"
|
|
23
|
-
? "inherit"
|
|
24
|
-
: "light";
|
|
6
|
+
scheme === "dark" ? "dark" : scheme === "inherit" ? "inherit" : "light";
|
|
25
7
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!theme)
|
|
8
|
+
export function applyTheme(element, input) {
|
|
9
|
+
if (!input)
|
|
29
10
|
return;
|
|
30
|
-
|
|
31
|
-
// when the field has no dark variant) via the shared engine, which sanitizes
|
|
32
|
-
// both sides - an invalid value yields "" and is skipped, so the Coras default
|
|
33
|
-
// (itself a light-dark pair) shows through. The dark side falls back to the
|
|
34
|
-
// Coras dark default when the brand sets only the light value.
|
|
11
|
+
const theme = migrateBrandConfig(input);
|
|
35
12
|
const dark = (theme.dark ?? {});
|
|
36
13
|
for (const key of THEME_KEYS) {
|
|
37
14
|
if (theme[key] == null && dark[key] == null)
|
|
38
15
|
continue;
|
|
39
|
-
const value
|
|
40
|
-
|
|
41
|
-
element.style.setProperty(THEME_CSS_VARS[key], value);
|
|
16
|
+
for (const [name, value] of brandVarEntries(key, theme[key], dark[key]))
|
|
17
|
+
element.style.setProperty(name, value);
|
|
42
18
|
}
|
|
43
|
-
if (theme.
|
|
44
|
-
const
|
|
45
|
-
if (shadow) {
|
|
46
|
-
element.style.setProperty("--coras-brand-navbar-shadow", shadow);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// The page background is opt-in (see the root rule in tokens.css): only an
|
|
50
|
-
// explicit `background` paints the embed surface, so the embed otherwise
|
|
51
|
-
// adopts the host page's background.
|
|
52
|
-
if (theme.background != null || dark.background != null) {
|
|
53
|
-
const pageBg = brandVarValue("background", theme.background, dark.background);
|
|
19
|
+
if (theme.pageBackground != null || dark.pageBackground != null) {
|
|
20
|
+
const pageBg = brandVarValue("pageBackground", theme.pageBackground, dark.pageBackground);
|
|
54
21
|
if (pageBg)
|
|
55
22
|
element.style.setProperty("--coras-page-background", pageBg);
|
|
56
23
|
}
|
|
@@ -62,12 +29,13 @@ export function applyTheme(element, theme) {
|
|
|
62
29
|
element.appendChild(style);
|
|
63
30
|
}
|
|
64
31
|
}
|
|
65
|
-
/** Remove any brand variables/fonts previously set on an element. */
|
|
66
32
|
export function clearTheme(element) {
|
|
67
33
|
for (const key of THEME_KEYS) {
|
|
68
34
|
element.style.removeProperty(THEME_CSS_VARS[key]);
|
|
69
35
|
}
|
|
70
|
-
|
|
36
|
+
for (const name of Object.values(BRAND_DARK_CSS_VARS)) {
|
|
37
|
+
element.style.removeProperty(name);
|
|
38
|
+
}
|
|
71
39
|
element.style.removeProperty("--coras-page-background");
|
|
72
40
|
element
|
|
73
41
|
.querySelectorAll(":scope > [data-coras-fontfaces]")
|