@coras-io/embed 0.2.0 → 0.3.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 +127 -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.styles.js +4 -0
- package/dist/containers/navbar/navbar.component.d.ts +5 -1
- package/dist/containers/navbar/navbar.component.js +38 -9
- package/dist/containers/navbar/navbar.styles.js +38 -12
- 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 +253 -238
- package/dist/locales/en-US.d.ts +100 -82
- package/dist/locales/en-US.js +105 -90
- 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 +104 -89
- 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 +150 -42
- 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 +1 -15
- package/dist/sdk/theme.js +4 -39
- package/dist/sdk/types.d.ts +55 -24
- 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 +105 -47
- 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 +360 -90
- package/dist/views/details-page/details-page.component.d.ts +5 -5
- package/dist/views/details-page/details-page.component.js +72 -79
- 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 +62 -75
- 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/url.d.ts
CHANGED
|
@@ -6,6 +6,23 @@ import type { CorasPageName, CorasPageParams } from "./types.js";
|
|
|
6
6
|
* URL; these helpers translate between a Coras state and a canonical URL so each
|
|
7
7
|
* framework does not write bespoke parsing glue. See ADR 0013.
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* Pages with a URL. `suggestion-widget` is absent by design: it is a standalone
|
|
11
|
+
* embed the host mounts directly, not a page a visitor navigates to.
|
|
12
|
+
*/
|
|
13
|
+
export type CorasRoutablePage = Exclude<CorasPageName, "suggestion-widget">;
|
|
14
|
+
/**
|
|
15
|
+
* A URL-representable state.
|
|
16
|
+
*
|
|
17
|
+
* `page` and `params` are deliberately left uncorrelated, and `page` stays the
|
|
18
|
+
* full union: a host that owns routing feeds these helpers straight from a
|
|
19
|
+
* `coras:navigate` detail or its own router, where the pair arrives as separate
|
|
20
|
+
* values no annotation can prove match. Narrowing here would buy nothing the
|
|
21
|
+
* runtime does not already check and would force every routed host to
|
|
22
|
+
* destructure per page. `buildCorasUrl` enforces the real constraints - a
|
|
23
|
+
* routable page, a details id, and an id that clears the reserved segments -
|
|
24
|
+
* and throws rather than emit a URL that parses back as something else.
|
|
25
|
+
*/
|
|
9
26
|
export type CorasUrlState = {
|
|
10
27
|
page: CorasPageName;
|
|
11
28
|
params: CorasPageParams;
|
|
@@ -23,12 +40,16 @@ export type CorasUrlStrategy = {
|
|
|
23
40
|
currencyParam?: string;
|
|
24
41
|
/**
|
|
25
42
|
* Path segment for pages that use a reserved keyword. `landing` has no
|
|
26
|
-
* segment and `details` is represented by a bare id segment.
|
|
43
|
+
* segment and `details` is represented by a bare, percent-encoded id segment.
|
|
44
|
+
* Reserved segments therefore carve their words out of the details id space:
|
|
45
|
+
* an id equal to one is rejected by `buildCorasUrl` rather than silently
|
|
46
|
+
* building a URL that parses back as another page. Rename them here if they
|
|
47
|
+
* collide with real ids.
|
|
27
48
|
*/
|
|
28
|
-
reservedSegments?: Partial<Record<
|
|
49
|
+
reservedSegments?: Partial<Record<CorasRoutablePage, string>>;
|
|
29
50
|
};
|
|
30
51
|
export declare const DEFAULT_URL_STRATEGY: Required<Omit<CorasUrlStrategy, "reservedSegments">> & {
|
|
31
|
-
reservedSegments: Partial<Record<
|
|
52
|
+
reservedSegments: Partial<Record<CorasRoutablePage, string>>;
|
|
32
53
|
};
|
|
33
54
|
/** Parse a (relative or absolute) URL into a Coras state. */
|
|
34
55
|
export declare function parseCorasUrl(url: string, strategy?: CorasUrlStrategy): CorasUrlState;
|
package/dist/sdk/url.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { normalizeCurrency, normalizeLocale, PAGE_PARAM_KEYS,
|
|
1
|
+
import { CorasValidationError, normalizeCurrency, normalizeLocale, PAGE_PARAM_KEYS, } from "./validation.js";
|
|
2
2
|
const DEFAULT_RESERVED = {
|
|
3
3
|
search: "search",
|
|
4
4
|
payment: "payment",
|
|
5
5
|
help: "help",
|
|
6
|
-
"suggestion-widget": "widget",
|
|
7
6
|
};
|
|
8
7
|
export const DEFAULT_URL_STRATEGY = {
|
|
9
8
|
basePath: "",
|
|
@@ -12,14 +11,33 @@ export const DEFAULT_URL_STRATEGY = {
|
|
|
12
11
|
currencyParam: "currency",
|
|
13
12
|
reservedSegments: DEFAULT_RESERVED,
|
|
14
13
|
};
|
|
14
|
+
function invalidUrlState(message, field) {
|
|
15
|
+
throw new CorasValidationError("invalid_page_params", message, field);
|
|
16
|
+
}
|
|
15
17
|
function resolveStrategy(strategy = {}) {
|
|
18
|
+
const reservedSegments = {
|
|
19
|
+
...DEFAULT_RESERVED,
|
|
20
|
+
...strategy.reservedSegments,
|
|
21
|
+
};
|
|
22
|
+
// A segment holding a `/` (or nothing) would split into a path shape the
|
|
23
|
+
// parser can never invert, and two pages sharing a segment make the parse
|
|
24
|
+
// ambiguous. Both are host configuration errors: fail loudly at the call
|
|
25
|
+
// rather than emit URLs that silently round-trip to the wrong page.
|
|
26
|
+
const seen = new Map();
|
|
27
|
+
for (const [page, segment] of Object.entries(reservedSegments)) {
|
|
28
|
+
if (!segment || segment !== segment.trim() || /[/?#]/.test(segment)) {
|
|
29
|
+
invalidUrlState(`reservedSegments.${page} must be a non-empty path segment without "/", "?" or "#".`, `reservedSegments.${page}`);
|
|
30
|
+
}
|
|
31
|
+
const owner = seen.get(segment);
|
|
32
|
+
if (owner) {
|
|
33
|
+
invalidUrlState(`reservedSegments."${segment}" is used by both "${owner}" and "${page}"; each page needs its own segment.`, `reservedSegments.${page}`);
|
|
34
|
+
}
|
|
35
|
+
seen.set(segment, page);
|
|
36
|
+
}
|
|
16
37
|
return {
|
|
17
38
|
...DEFAULT_URL_STRATEGY,
|
|
18
39
|
...strategy,
|
|
19
|
-
reservedSegments
|
|
20
|
-
...DEFAULT_RESERVED,
|
|
21
|
-
...strategy.reservedSegments,
|
|
22
|
-
},
|
|
40
|
+
reservedSegments,
|
|
23
41
|
};
|
|
24
42
|
}
|
|
25
43
|
function reservedToPage(reserved) {
|
|
@@ -33,22 +51,15 @@ function reservedToPage(reserved) {
|
|
|
33
51
|
function trimSlashes(value) {
|
|
34
52
|
return value.replace(/^\/+|\/+$/g, "");
|
|
35
53
|
}
|
|
36
|
-
// Decode a
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return value
|
|
44
|
-
}
|
|
45
|
-
if (type === "boolean") {
|
|
46
|
-
if (value === "true")
|
|
47
|
-
return true;
|
|
48
|
-
if (value === "false")
|
|
49
|
-
return false;
|
|
54
|
+
// Decode a percent-encoded path segment, falling back to the raw value when it
|
|
55
|
+
// holds a malformed escape (so a hand-written URL is never a hard failure).
|
|
56
|
+
function safeDecodeSegment(value) {
|
|
57
|
+
try {
|
|
58
|
+
return decodeURIComponent(value);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return value;
|
|
50
62
|
}
|
|
51
|
-
return value;
|
|
52
63
|
}
|
|
53
64
|
/** Parse a (relative or absolute) URL into a Coras state. */
|
|
54
65
|
export function parseCorasUrl(url, strategy = {}) {
|
|
@@ -89,6 +100,7 @@ export function parseCorasUrl(url, strategy = {}) {
|
|
|
89
100
|
}
|
|
90
101
|
const reservedMap = reservedToPage(resolved.reservedSegments);
|
|
91
102
|
const pageSegment = segments[index];
|
|
103
|
+
// Only ever `landing`, a reserved page, or `details` - all routable.
|
|
92
104
|
let page = "landing";
|
|
93
105
|
const params = {};
|
|
94
106
|
if (pageSegment) {
|
|
@@ -97,8 +109,9 @@ export function parseCorasUrl(url, strategy = {}) {
|
|
|
97
109
|
page = reservedPage;
|
|
98
110
|
}
|
|
99
111
|
else {
|
|
112
|
+
// Any non-reserved segment is a (percent-encoded) details id.
|
|
100
113
|
page = "details";
|
|
101
|
-
params.id = pageSegment;
|
|
114
|
+
params.id = safeDecodeSegment(pageSegment);
|
|
102
115
|
}
|
|
103
116
|
}
|
|
104
117
|
// Page-specific params come from the query string.
|
|
@@ -108,13 +121,18 @@ export function parseCorasUrl(url, strategy = {}) {
|
|
|
108
121
|
continue; // details id is the path segment
|
|
109
122
|
if (key === "metadata")
|
|
110
123
|
continue; // host context, not URL state
|
|
124
|
+
// Every param a routable page accepts is a string, so the raw query value
|
|
125
|
+
// is already the type `mount()` validation expects.
|
|
111
126
|
const value = parsed.searchParams.get(key);
|
|
112
127
|
if (value != null)
|
|
113
|
-
params[key] =
|
|
128
|
+
params[key] = value;
|
|
114
129
|
}
|
|
130
|
+
// `page` and `params` are built together above (a details page always got its
|
|
131
|
+
// id from the path segment), so the pairing holds even though the locals are
|
|
132
|
+
// separate values the compiler cannot correlate.
|
|
115
133
|
return {
|
|
116
134
|
page,
|
|
117
|
-
params
|
|
135
|
+
params,
|
|
118
136
|
...(locale ? { locale } : {}),
|
|
119
137
|
...(currency ? { currency } : {}),
|
|
120
138
|
};
|
|
@@ -122,6 +140,11 @@ export function parseCorasUrl(url, strategy = {}) {
|
|
|
122
140
|
/** Build a canonical URL from a Coras state. */
|
|
123
141
|
export function buildCorasUrl(state, strategy = {}) {
|
|
124
142
|
const resolved = resolveStrategy(strategy);
|
|
143
|
+
// The type already excludes it; this catches JavaScript hosts, for whom a
|
|
144
|
+
// silent landing URL would be the alternative.
|
|
145
|
+
if (state.page === "suggestion-widget") {
|
|
146
|
+
invalidUrlState("suggestion-widget has no URL: it is a standalone embed the host mounts directly, not a routable page.", "page");
|
|
147
|
+
}
|
|
125
148
|
const segments = [];
|
|
126
149
|
const base = trimSlashes(resolved.basePath);
|
|
127
150
|
if (base)
|
|
@@ -132,8 +155,22 @@ export function buildCorasUrl(state, strategy = {}) {
|
|
|
132
155
|
segments.push(state.currency);
|
|
133
156
|
const params = (state.params ?? {});
|
|
134
157
|
if (state.page === "details") {
|
|
135
|
-
|
|
136
|
-
|
|
158
|
+
const id = params.id;
|
|
159
|
+
// The id *is* the route segment, so a details state without one has no URL.
|
|
160
|
+
// Building the bare landing path instead would lose the page silently.
|
|
161
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
162
|
+
invalidUrlState("A details state needs a non-empty params.id: the id is its URL segment.", "params.id");
|
|
163
|
+
}
|
|
164
|
+
// A bare id segment shares its space with the reserved words, so an id that
|
|
165
|
+
// equals one would parse back as that page. Reject it rather than emit a
|
|
166
|
+
// URL that does not round-trip; rename the segment via `reservedSegments`
|
|
167
|
+
// if a real id collides.
|
|
168
|
+
const collision = Object.entries(resolved.reservedSegments).find(([, segment]) => segment === id);
|
|
169
|
+
if (collision) {
|
|
170
|
+
invalidUrlState(`A details id of "${id}" collides with the "${collision[0]}" route segment and would parse back as that page. Rename the segment via reservedSegments.`, "params.id");
|
|
171
|
+
}
|
|
172
|
+
// Encode the id so `/`, `?`, and `#` survive the round-trip.
|
|
173
|
+
segments.push(encodeURIComponent(id));
|
|
137
174
|
}
|
|
138
175
|
else {
|
|
139
176
|
const reserved = resolved.reservedSegments[state.page];
|
|
@@ -160,9 +197,11 @@ export function buildCorasUrl(state, strategy = {}) {
|
|
|
160
197
|
}
|
|
161
198
|
/** Normalize locale/currency aliases and ensure a params object. */
|
|
162
199
|
export function normalizeCorasUrlState(input) {
|
|
200
|
+
// Only locale and currency are rewritten, so the page/params pairing the
|
|
201
|
+
// caller supplied carries through untouched.
|
|
163
202
|
return {
|
|
164
203
|
page: input.page,
|
|
165
|
-
params:
|
|
204
|
+
params: input.params ?? {},
|
|
166
205
|
...(input.locale
|
|
167
206
|
? { locale: normalizeLocale(input.locale) ?? input.locale }
|
|
168
207
|
: {}),
|
package/dist/sdk/validation.d.ts
CHANGED
|
@@ -21,13 +21,6 @@ export declare const PUBLIC_PAGES: readonly CorasPageName[];
|
|
|
21
21
|
export declare function assertValidPage(page: unknown): asserts page is CorasPageName;
|
|
22
22
|
/** Allowed param keys per page (public names). */
|
|
23
23
|
export declare const PAGE_PARAM_KEYS: Record<CorasPageName, readonly string[]>;
|
|
24
|
-
/**
|
|
25
|
-
* URL params whose string form decodes to a non-string type. Everything not
|
|
26
|
-
* listed stays a string. Used by the URL helpers so a value round-trips through
|
|
27
|
-
* a URL as the type `mount()` validation expects (e.g. `limit=3` -> `3`, not
|
|
28
|
-
* `"3"`; `showExploreCard=false` -> `false`, not a truthy `"false"`).
|
|
29
|
-
*/
|
|
30
|
-
export declare const PAGE_PARAM_TYPES: Readonly<Record<string, "number" | "boolean">>;
|
|
31
24
|
/** Normalize locale casing/separators (e.g. `en_ie`, `EN-IE` -> `en-IE`). */
|
|
32
25
|
export declare function normalizeLocale(value: string): SupportedLocales | undefined;
|
|
33
26
|
/** Normalize currency casing (e.g. `eur` -> `EUR`). */
|
package/dist/sdk/validation.js
CHANGED
|
@@ -52,15 +52,31 @@ export const PAGE_PARAM_KEYS = {
|
|
|
52
52
|
"suggestion-widget": ["limit", "showExploreCard", "country", "city"],
|
|
53
53
|
};
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `"3"`; `showExploreCard=false` -> `false`, not a truthy `"false"`).
|
|
55
|
+
* The value type of every public page param. Param names mean the same thing on
|
|
56
|
+
* every page that accepts them, so one flat table drives both `mount()`
|
|
57
|
+
* validation and the URL helpers' decoding.
|
|
59
58
|
*/
|
|
60
|
-
|
|
59
|
+
const PAGE_PARAM_KINDS = {
|
|
60
|
+
country: "string",
|
|
61
|
+
city: "string",
|
|
62
|
+
startDate: "date",
|
|
63
|
+
endDate: "date",
|
|
64
|
+
date: "date",
|
|
65
|
+
sort: "string",
|
|
66
|
+
duration: "string",
|
|
67
|
+
price: "string",
|
|
68
|
+
categories: "string",
|
|
69
|
+
search: "string",
|
|
70
|
+
id: "string",
|
|
71
|
+
content: "string",
|
|
72
|
+
metadata: "metadata",
|
|
61
73
|
limit: "number",
|
|
62
74
|
showExploreCard: "boolean",
|
|
63
75
|
};
|
|
76
|
+
/** Dates cross the API as plain `yyyy-MM-dd` calendar days. */
|
|
77
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
78
|
+
/** Upper bound for the suggestion widget's `limit`, guarding absurd requests. */
|
|
79
|
+
const MAX_WIDGET_LIMIT = 50;
|
|
64
80
|
const CONFIG_KEYS = [
|
|
65
81
|
"apiUrl",
|
|
66
82
|
"distributorId",
|
|
@@ -75,14 +91,14 @@ const CONFIG_KEYS = [
|
|
|
75
91
|
];
|
|
76
92
|
const localeSet = new Set(allLocales);
|
|
77
93
|
const currencySet = new Set(supportedCurrencies);
|
|
78
|
-
const colorSchemes = new Set([
|
|
79
|
-
"light",
|
|
80
|
-
"dark",
|
|
81
|
-
"system",
|
|
82
|
-
"inherit",
|
|
83
|
-
]);
|
|
94
|
+
const colorSchemes = new Set(["light", "dark", "inherit"]);
|
|
84
95
|
/** Normalize locale casing/separators (e.g. `en_ie`, `EN-IE` -> `en-IE`). */
|
|
85
96
|
export function normalizeLocale(value) {
|
|
97
|
+
// Guard the type here rather than at each call site: JavaScript hosts reach
|
|
98
|
+
// this with arbitrary values, and an unsupported one is `undefined` (which
|
|
99
|
+
// callers turn into a typed error) rather than a raw TypeError from `.trim`.
|
|
100
|
+
if (typeof value !== "string")
|
|
101
|
+
return undefined;
|
|
86
102
|
const parts = value.trim().replace(/_/g, "-").split("-");
|
|
87
103
|
if (parts.length === 0 || !parts[0])
|
|
88
104
|
return undefined;
|
|
@@ -93,6 +109,8 @@ export function normalizeLocale(value) {
|
|
|
93
109
|
}
|
|
94
110
|
/** Normalize currency casing (e.g. `eur` -> `EUR`). */
|
|
95
111
|
export function normalizeCurrency(value) {
|
|
112
|
+
if (typeof value !== "string")
|
|
113
|
+
return undefined;
|
|
96
114
|
const canonical = value.trim().toUpperCase();
|
|
97
115
|
return currencySet.has(canonical)
|
|
98
116
|
? canonical
|
|
@@ -122,17 +140,15 @@ export function assertSecureUrl(value, field) {
|
|
|
122
140
|
return url.href;
|
|
123
141
|
throw new CorasValidationError("invalid_url", `${field} must use https (http is only allowed for localhost).`, field);
|
|
124
142
|
}
|
|
125
|
-
// Strict hostname: alnum labels separated by dots, optional :port.
|
|
126
|
-
// Rejects protocol-relative inputs, paths, and free-form strings.
|
|
127
|
-
const BARE_HOSTNAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*(?::\d{1,5})?$/i;
|
|
128
143
|
/**
|
|
129
144
|
* Asset URL must be one of:
|
|
130
|
-
* - an https URL (http allowed only for localhost),
|
|
131
|
-
* - a bare hostname like `assets.example.com[:port]`,
|
|
145
|
+
* - an absolute https URL (http allowed only for localhost),
|
|
132
146
|
* - a root-relative path like `/assets`.
|
|
133
|
-
* Rejected: protocol-relative URLs (`//host/x`),
|
|
134
|
-
*
|
|
135
|
-
*
|
|
147
|
+
* Rejected: protocol-relative URLs (`//host/x`), bare hostnames
|
|
148
|
+
* (`assets.example.com` - the browser would resolve it against the host page's
|
|
149
|
+
* origin), `javascript:` / `data:` URIs, and traversal-style paths. `assetsUrl`
|
|
150
|
+
* is interpolated straight into image and SVG hrefs (see icon.component.ts), so
|
|
151
|
+
* a permissive validator is a foot-gun.
|
|
136
152
|
*/
|
|
137
153
|
function validateAssetsUrl(value) {
|
|
138
154
|
if (value === "") {
|
|
@@ -160,9 +176,7 @@ function validateAssetsUrl(value) {
|
|
|
160
176
|
assertSecureUrl(value, "config.assetsUrl");
|
|
161
177
|
return;
|
|
162
178
|
}
|
|
163
|
-
|
|
164
|
-
throw new CorasValidationError("invalid_url", "config.assetsUrl must be an https URL, a bare hostname, or a root-relative path.", "config.assetsUrl");
|
|
165
|
-
}
|
|
179
|
+
throw new CorasValidationError("invalid_url", "config.assetsUrl must be an absolute https URL or a root-relative path.", "config.assetsUrl");
|
|
166
180
|
}
|
|
167
181
|
// Field keys plus the BrandConfig meta keys validateBrandConfig understands.
|
|
168
182
|
// Used only to surface unknown keys in strict mode (the schema itself strips
|
|
@@ -170,6 +184,9 @@ function validateAssetsUrl(value) {
|
|
|
170
184
|
const THEME_KEYS = [
|
|
171
185
|
...FIELDS.map((field) => field.key),
|
|
172
186
|
"logo",
|
|
187
|
+
"logoDark",
|
|
188
|
+
"navbarLogo",
|
|
189
|
+
"footerLogo",
|
|
173
190
|
"fontFaces",
|
|
174
191
|
"dark",
|
|
175
192
|
"schemaVersion",
|
|
@@ -198,6 +215,13 @@ function validateTheme(theme, strict) {
|
|
|
198
215
|
}
|
|
199
216
|
}
|
|
200
217
|
}
|
|
218
|
+
// Guards a `.map()` over host-supplied config: a JavaScript caller passing a
|
|
219
|
+
// bare string would otherwise throw a raw TypeError instead of a typed error.
|
|
220
|
+
function assertArray(value, field) {
|
|
221
|
+
if (!Array.isArray(value)) {
|
|
222
|
+
throw new CorasValidationError("invalid_config", `${field} must be an array.`, field);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
201
225
|
function rejectUnknownKeys(object, allowed, context) {
|
|
202
226
|
for (const key of Object.keys(object)) {
|
|
203
227
|
if (!allowed.includes(key)) {
|
|
@@ -244,9 +268,15 @@ export function validateConfig(config, strict) {
|
|
|
244
268
|
}
|
|
245
269
|
if (config.colorScheme != null &&
|
|
246
270
|
!colorSchemes.has(config.colorScheme)) {
|
|
247
|
-
throw new CorasValidationError("invalid_color_scheme", `Unsupported colorScheme "${config.colorScheme}". Valid values: light, dark,
|
|
271
|
+
throw new CorasValidationError("invalid_color_scheme", `Unsupported colorScheme "${config.colorScheme}". Valid values: light, dark, inherit.`, "config.colorScheme");
|
|
272
|
+
}
|
|
273
|
+
if (config.loyaltyPointsEnabled != null) {
|
|
274
|
+
if (typeof config.loyaltyPointsEnabled !== "boolean") {
|
|
275
|
+
throw new CorasValidationError("invalid_config", "config.loyaltyPointsEnabled must be a boolean.", "config.loyaltyPointsEnabled");
|
|
276
|
+
}
|
|
248
277
|
}
|
|
249
278
|
if (config.allowedLocales != null) {
|
|
279
|
+
assertArray(config.allowedLocales, "config.allowedLocales");
|
|
250
280
|
normalized.allowedLocales = config.allowedLocales.map((value) => {
|
|
251
281
|
const locale = normalizeLocale(value);
|
|
252
282
|
if (!locale) {
|
|
@@ -256,6 +286,7 @@ export function validateConfig(config, strict) {
|
|
|
256
286
|
});
|
|
257
287
|
}
|
|
258
288
|
if (config.allowedCurrencies != null) {
|
|
289
|
+
assertArray(config.allowedCurrencies, "config.allowedCurrencies");
|
|
259
290
|
normalized.allowedCurrencies = config.allowedCurrencies.map((value) => {
|
|
260
291
|
const currency = normalizeCurrency(value);
|
|
261
292
|
if (!currency) {
|
|
@@ -269,40 +300,67 @@ export function validateConfig(config, strict) {
|
|
|
269
300
|
export function validatePageParams(page, params, strict) {
|
|
270
301
|
if (params == null)
|
|
271
302
|
return {};
|
|
272
|
-
|
|
303
|
+
// An array is `typeof "object"`, but it is never a valid params bag: accepting
|
|
304
|
+
// one would silently ignore every param the host meant to pass.
|
|
305
|
+
if (typeof params !== "object" || Array.isArray(params)) {
|
|
273
306
|
throw new CorasValidationError("invalid_page_params", `params for page "${page}" must be an object.`, "params");
|
|
274
307
|
}
|
|
275
308
|
const allowed = PAGE_PARAM_KEYS[page];
|
|
276
309
|
if (strict) {
|
|
277
310
|
rejectUnknownKeys(params, allowed, "params");
|
|
278
311
|
}
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
312
|
+
// Type-check every param the page accepts. Outside strict mode unknown keys
|
|
313
|
+
// are ignored rather than rejected, so only `allowed` keys are checked.
|
|
314
|
+
for (const key of allowed) {
|
|
315
|
+
const value = params[key];
|
|
316
|
+
// An explicit `undefined`/`null` clears the param back to its default.
|
|
317
|
+
if (value == null)
|
|
318
|
+
continue;
|
|
319
|
+
assertParamKind(page, key, value);
|
|
285
320
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
321
|
+
return params;
|
|
322
|
+
}
|
|
323
|
+
function assertParamKind(page, key, value) {
|
|
324
|
+
const invalid = (expected) => {
|
|
325
|
+
throw new CorasValidationError("invalid_page_params", `params.${key} for page "${page}" must be ${expected}.`, `params.${key}`);
|
|
326
|
+
};
|
|
327
|
+
switch (PAGE_PARAM_KINDS[key] ?? "string") {
|
|
328
|
+
case "number":
|
|
329
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
330
|
+
invalid("a number");
|
|
331
|
+
break;
|
|
332
|
+
case "boolean":
|
|
333
|
+
if (typeof value !== "boolean")
|
|
334
|
+
invalid("a boolean");
|
|
335
|
+
break;
|
|
336
|
+
case "date":
|
|
337
|
+
// Pages clear a date by emitting `""` (see the landing page's date reset),
|
|
338
|
+
// which means the same as omitting the param: reset it to its default.
|
|
339
|
+
if (typeof value !== "string" || (value !== "" && !ISO_DATE.test(value)))
|
|
340
|
+
invalid("a yyyy-MM-dd date");
|
|
341
|
+
break;
|
|
342
|
+
case "metadata": {
|
|
343
|
+
// A free-form bag of client data; its keys are arbitrary, but every value
|
|
344
|
+
// must be a string so it can be serialized for the API forward.
|
|
345
|
+
const isPlainObject = typeof value === "object" && !Array.isArray(value);
|
|
293
346
|
if (!isPlainObject ||
|
|
294
|
-
!Object.values(
|
|
295
|
-
|
|
296
|
-
|
|
347
|
+
!Object.values(value).every((v) => typeof v === "string"))
|
|
348
|
+
invalid("an object of string values");
|
|
349
|
+
break;
|
|
297
350
|
}
|
|
351
|
+
default:
|
|
352
|
+
if (typeof value !== "string")
|
|
353
|
+
invalid("a string");
|
|
354
|
+
break;
|
|
298
355
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
356
|
+
// `id` is the public identifier and additionally carries the details route.
|
|
357
|
+
if (key === "id" && value.length === 0)
|
|
358
|
+
invalid("a non-empty string");
|
|
359
|
+
if (key === "limit" &&
|
|
360
|
+
(!Number.isInteger(value) ||
|
|
361
|
+
value <= 0 ||
|
|
362
|
+
value > MAX_WIDGET_LIMIT))
|
|
363
|
+
invalid(`a whole number between 1 and ${MAX_WIDGET_LIMIT}`);
|
|
306
364
|
}
|
|
307
365
|
/**
|
|
308
366
|
* Validate and normalize the full mount options before anything is rendered.
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { ContrastPair } from "./contrast.js";
|
|
2
|
-
/**
|
|
3
|
-
* Default-theme contrast pairs validated in CI (plan F-004, P-058; pairs from
|
|
4
|
-
* Appendix J). `tokens` lists the CSS variables each colour resolves from so a
|
|
5
|
-
* stale check can confirm they still exist in the generated token sheet.
|
|
6
|
-
*/
|
|
7
2
|
export type DefaultContrastPair = ContrastPair & {
|
|
8
3
|
tokens: [string, string];
|
|
9
4
|
};
|
|
@@ -17,17 +17,17 @@ export const DEFAULT_CONTRAST_PAIRS = [
|
|
|
17
17
|
tokens: ["--coras-color-text", "--coras-color-background"],
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
|
-
label: "
|
|
21
|
-
foreground: "#
|
|
20
|
+
label: "Muted/label text on page background",
|
|
21
|
+
foreground: "#888888",
|
|
22
22
|
background: "#ffffff",
|
|
23
|
-
threshold: "AA-
|
|
24
|
-
tokens: ["--coras-color-text-
|
|
23
|
+
threshold: "AA-large",
|
|
24
|
+
tokens: ["--coras-color-text-muted", "--coras-color-background"],
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
|
-
label: "
|
|
27
|
+
label: "Brand accent on background",
|
|
28
28
|
foreground: "#161c44",
|
|
29
29
|
background: "#ffffff",
|
|
30
|
-
threshold: "AA-
|
|
30
|
+
threshold: "AA-large",
|
|
31
31
|
tokens: ["--coras-brand-secondary", "--coras-color-background"],
|
|
32
32
|
},
|
|
33
33
|
{
|
|
@@ -35,14 +35,14 @@ export const DEFAULT_CONTRAST_PAIRS = [
|
|
|
35
35
|
foreground: "#888888",
|
|
36
36
|
background: "#fcfcff",
|
|
37
37
|
threshold: "AA-large",
|
|
38
|
-
tokens: ["--coras-color-text-muted", "--coras-color-
|
|
38
|
+
tokens: ["--coras-color-text-muted", "--coras-color-card"],
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
label: "Error alert text on error alert background",
|
|
42
42
|
foreground: "#c9224a",
|
|
43
|
-
background: "#
|
|
43
|
+
background: "#fae8ec",
|
|
44
44
|
threshold: "AA-normal",
|
|
45
|
-
tokens: ["--coras-color-error", "--coras-color-error-
|
|
45
|
+
tokens: ["--coras-color-error", "--coras-color-error-background"],
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
label: "Focus ring on background",
|
|
@@ -58,4 +58,74 @@ export const DEFAULT_CONTRAST_PAIRS = [
|
|
|
58
58
|
threshold: "AA-normal",
|
|
59
59
|
tokens: ["--coras-navbar-text-color", "--coras-navbar-background"],
|
|
60
60
|
},
|
|
61
|
+
{
|
|
62
|
+
label: "Body text on page background (dark)",
|
|
63
|
+
foreground: "#ffffff",
|
|
64
|
+
background: "#101216",
|
|
65
|
+
threshold: "AA-normal",
|
|
66
|
+
tokens: ["--coras-color-text", "--coras-color-background"],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
label: "Muted/label text on page background (dark)",
|
|
70
|
+
foreground: "#cccccc",
|
|
71
|
+
background: "#101216",
|
|
72
|
+
threshold: "AA-large",
|
|
73
|
+
tokens: ["--coras-color-text-muted", "--coras-color-background"],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
label: "Muted/placeholder text on surface (dark)",
|
|
77
|
+
foreground: "#cccccc",
|
|
78
|
+
background: "#191c22",
|
|
79
|
+
threshold: "AA-large",
|
|
80
|
+
tokens: ["--coras-color-text-muted", "--coras-color-card"],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
label: "Focus ring on background (dark)",
|
|
84
|
+
foreground: "#4657d4",
|
|
85
|
+
background: "#101216",
|
|
86
|
+
threshold: "AA-large",
|
|
87
|
+
tokens: ["--coras-color-focus-ring", "--coras-color-background"],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
label: "Navbar text on navbar background (dark)",
|
|
91
|
+
foreground: "#ffffff",
|
|
92
|
+
background: "#101216",
|
|
93
|
+
threshold: "AA-normal",
|
|
94
|
+
tokens: ["--coras-navbar-text-color", "--coras-navbar-background"],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
label: "Footer text on footer background (dark)",
|
|
98
|
+
foreground: "#ffffff",
|
|
99
|
+
background: "#191c22",
|
|
100
|
+
threshold: "AA-normal",
|
|
101
|
+
tokens: ["--coras-footer-text-color", "--coras-footer-background-color"],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: "Text button on surface (dark)",
|
|
105
|
+
foreground: "#6279f9",
|
|
106
|
+
background: "#191c22",
|
|
107
|
+
threshold: "AA-normal",
|
|
108
|
+
tokens: ["--coras-color-text-button", "--coras-color-card"],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
label: "Link on page background (dark)",
|
|
112
|
+
foreground: "#6279f9",
|
|
113
|
+
background: "#101216",
|
|
114
|
+
threshold: "AA-normal",
|
|
115
|
+
tokens: ["--coras-color-link", "--coras-color-background"],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
label: "Error alert text on error alert background (dark)",
|
|
119
|
+
foreground: "#f5748a",
|
|
120
|
+
background: "#442e36",
|
|
121
|
+
threshold: "AA-normal",
|
|
122
|
+
tokens: ["--coras-color-error", "--coras-color-error-background"],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
label: "Info alert text on info alert background (dark)",
|
|
126
|
+
foreground: "#ba86e1",
|
|
127
|
+
background: "#373045",
|
|
128
|
+
threshold: "AA-normal",
|
|
129
|
+
tokens: ["--coras-color-info", "--coras-color-info-background"],
|
|
130
|
+
},
|
|
61
131
|
];
|
package/dist/styles/contrast.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// WCAG contrast utilities (plan F-004, P-058).
|
|
2
|
-
// default-theme CI check
|
|
3
|
-
//
|
|
1
|
+
// WCAG contrast utilities (plan F-004, P-058). Internal to the embed: they back
|
|
2
|
+
// the default-theme CI check, which needs a browser to resolve any CSS colour
|
|
3
|
+
// (`oklch()`, named colours) by rasterizing it. `@coras-io/brand-tokens` carries
|
|
4
|
+
// the portable, parser-based equivalents for hosts and the Studio.
|
|
4
5
|
const MIN_RATIO = {
|
|
5
6
|
// AA normal text and AA UI/large both per WCAG 1.4.3 / 1.4.11.
|
|
6
7
|
"AA-normal": 4.5,
|
|
@@ -17,6 +17,22 @@ export default css `
|
|
|
17
17
|
box-sizing: inherit;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
:host button,
|
|
21
|
+
:host input,
|
|
22
|
+
:host select,
|
|
23
|
+
:host textarea {
|
|
24
|
+
font-family: inherit;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:host h1,
|
|
28
|
+
:host h2,
|
|
29
|
+
:host h3,
|
|
30
|
+
:host h4,
|
|
31
|
+
:host h5,
|
|
32
|
+
:host h6 {
|
|
33
|
+
font-family: var(--coras-font-family-heading);
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
[hidden] {
|
|
21
37
|
display: none !important;
|
|
22
38
|
}
|
|
@@ -26,6 +42,6 @@ export default css `
|
|
|
26
42
|
:host a:active {
|
|
27
43
|
text-decoration: none;
|
|
28
44
|
font-weight: 600;
|
|
29
|
-
color: var(--coras-color-primary
|
|
45
|
+
color: var(--coras-color-primary);
|
|
30
46
|
}
|
|
31
47
|
`;
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared CSS primitives (plan F-004, P-063). Components compose these instead
|
|
3
|
-
* of re-authoring focus rings, screen-reader helpers, or skeleton shimmer, so
|
|
4
|
-
* accessibility and branding stay consistent. All values resolve from the
|
|
5
|
-
* semantic token tier.
|
|
6
|
-
*/
|
|
7
|
-
/** Visible focus ring using the brand focus token. */
|
|
8
1
|
export declare const focusRing: import("lit").CSSResult;
|
|
9
|
-
/** Accessible "visually hidden" helper that remains in the accessibility tree. */
|
|
10
2
|
export declare const visuallyHidden: import("lit").CSSResult;
|
|
11
|
-
/** Loading skeleton shimmer; respects reduced-motion. */
|
|
12
3
|
export declare const skeleton: import("lit").CSSResult;
|
|
13
|
-
/** Reset for native controls used as branded buttons/fields. */
|
|
14
4
|
export declare const controlReset: import("lit").CSSResult;
|