diff --git a/README.md b/README.md
index 09b48ff..a41421b 100644
--- a/README.md
+++ b/README.md
@@ -75,10 +75,66 @@ use `topsort-banner-slot` as children elements.
| location | Optional String | The location for geotargeting |
| new-tab | Optional Boolean | Opens the banner's link in a new tab (defaults to false) |
| context | Optional Boolean | Uses the element as a context provider to render multiple banners |
+| class | Optional String | Custom CSS class to apply to the banner container |
\* Only one of `[category-id, category-ids, category-disjunctions]` must be set.
If multiple are set, only the first will be considered, in that order.
+# Styling
+
+The banner component is designed to integrate seamlessly with your existing CSS system.
+Each banner is rendered inside a container div with the class `ts-banner`,
+making it easy to target with CSS selectors.
+
+## CSS Targeting
+
+You can style banners using standard CSS selectors:
+
+```css
+/* Target all banner containers */
+.ts-banner {
+ padding: 10px;
+ margin: 10px;
+ border: 1px solid #ccc;
+}
+
+/* Target banners with specific dimensions */
+.ts-banner[data-ts-width="800"] {
+ max-width: 800px;
+}
+```
+
+## Custom CSS Classes
+
+You can pass custom CSS classes to the banner component using the `class` attribute:
+
+```html
+
diff --git a/src/index.ts b/src/index.ts
index 4d1457e..3c725a3 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -73,9 +73,10 @@ function getNoWinnersElement(): TemplateResult {
function getBannerElement(
banner: Banner,
- width: number,
- height: number,
newTab: boolean,
+ width?: number,
+ height?: number,
+ bannerClass?: string,
): TemplateResult {
if (window.TS_BANNERS.getBannerElement) {
const element = window.TS_BANNERS.getBannerElement(banner);
@@ -102,19 +103,39 @@ function getBannerElement(
return false;
}
})();
+
+ const containerClass = bannerClass ? `ts-banner ${bannerClass}` : "ts-banner";
+
+ const containerStyle = [
+ "display: block",
+ width ? `--ts-banner-width: ${width}px` : "",
+ height ? `--ts-banner-height: ${height}px` : "",
+ ]
+ .filter(Boolean)
+ .join("; ");
+
+ // let CSS cascade
+ const mediaStyle =
+ width && height
+ ? `width: ${width}px; height: ${height}px; object-fit: cover;`
+ : width
+ ? `width: ${width}px; height: auto; object-fit: cover;`
+ : height
+ ? `width: 100%; height: ${height}px; object-fit: cover;`
+ : "width: 100%; height: auto; object-fit: cover;";
+
const media = isVideo
? html`
`
: html`
`;
@@ -124,9 +145,12 @@ function getBannerElement(
: html`${media}`;
return html`
${wrappedMedia}
@@ -145,6 +169,7 @@ const bannerContextHasChanged = (newVal: BannerContext, oldVal?: BannerContext)
newVal.width !== oldVal.width ||
newVal.height !== oldVal.height ||
newVal.newTab !== oldVal.newTab ||
+ newVal.bannerClass !== oldVal.bannerClass ||
!!newVal.error !== !!oldVal.error ||
newVal.banners?.length !== oldVal.banners?.length
);
@@ -181,6 +206,7 @@ export class TopsortBanner extends BannerComponent(LitElement) {
width: this.width,
height: this.height,
newTab: this.newTab,
+ bannerClass: this.bannerClass,
};
@property({ type: Boolean, attribute: "context" })
@@ -203,7 +229,7 @@ export class TopsortBanner extends BannerComponent(LitElement) {
if (!banners.length) {
return getNoWinnersElement();
}
- return getBannerElement(banners[0], this.width, this.height, this.newTab);
+ return getBannerElement(banners[0], this.newTab, this.width, this.height, this.bannerClass);
},
error: (error) => getErrorElement(error),
});
@@ -221,13 +247,15 @@ export class TopsortBanner extends BannerComponent(LitElement) {
if (
changedProperties.has("width") ||
changedProperties.has("height") ||
- changedProperties.has("newTab")
+ changedProperties.has("newTab") ||
+ changedProperties.has("bannerClass")
) {
Promise.resolve().then(() => {
this.context = {
width: this.width,
height: this.height,
newTab: this.newTab,
+ bannerClass: this.bannerClass,
};
});
}
@@ -263,9 +291,10 @@ export class TopsortBannerSlot extends LitElement {
}
return getBannerElement(
this.context.banners[this.rank - 1],
+ this.context.newTab,
this.context.width,
this.context.height,
- this.context.newTab,
+ this.context.bannerClass,
);
}
@@ -278,8 +307,7 @@ export class TopsortBannerSlot extends LitElement {
@customElement("hls-video")
export class HlsVideo extends LitElement {
@property({ type: String }) src = ""; // HLS manifest URL
- @property({ type: String }) width = "800px";
- @property({ type: String }) height = "400px";
+ @property({ type: String }) styles = "";
private get videoId() {
try {
@@ -297,6 +325,7 @@ export class HlsVideo extends LitElement {
autoplay
loop
playsinline
+ style=${this.styles}
>
`;
}
@@ -305,10 +334,6 @@ export class HlsVideo extends LitElement {
const video = this.shadowRoot?.getElementById(this.videoId) as HTMLVideoElement;
if (!video) return;
- video.style.width = this.width;
- video.style.height = this.height;
- video.style.objectFit = "cover";
-
let Hls: HlsConstructor;
try {
Hls = await hlsDependency.load();
diff --git a/src/mixin.ts b/src/mixin.ts
index 6c9f619..6ee9e37 100644
--- a/src/mixin.ts
+++ b/src/mixin.ts
@@ -16,6 +16,7 @@ export declare class BannerComponentInterface {
searchQuery?: string;
location?: string;
newTab: boolean;
+ bannerClass?: string;
emitEvent(status: string): void;
buildAuction(slots: number): Auction;
@@ -50,6 +51,9 @@ export const BannerComponent = >(Base: T) => {
@property({ attribute: "new-tab", type: Boolean })
readonly newTab: boolean = false;
+ @property({ attribute: "class", type: String })
+ readonly bannerClass?: string;
+
buildAuction(slots: number): Auction {
const device = getDeviceType();
const auction: Auction = {
diff --git a/src/types.ts b/src/types.ts
index 709edfa..6ff618b 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -26,6 +26,7 @@ export interface BannerContext {
width: number;
height: number;
newTab: boolean;
+ bannerClass?: string;
banners?: Banner[];
error?: unknown;
}