@quikturn/logos-angular 1.0.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/README.md ADDED
@@ -0,0 +1,455 @@
1
+ # @quikturn/logos-angular
2
+
3
+ > Angular components for the [Quikturn Logos API](https://getquikturn.io) -- drop-in logo display, infinite carousel, and responsive grid. Built with standalone components and signal inputs for Angular 17+.
4
+
5
+ **[Get your API key](https://getquikturn.io)** -- free tier available, no credit card required.
6
+
7
+ ## Features
8
+
9
+ - **`<quikturn-logo>`** -- single logo image with lazy loading, optional link wrapper
10
+ - **`<quikturn-logo-carousel>`** -- infinite scrolling logo ticker (horizontal or vertical)
11
+ - **`<quikturn-logo-grid>`** -- responsive CSS grid of logos with custom template support
12
+ - **`provideQuikturnLogos()`** -- DI-based configuration for token and base URL
13
+ - **`injectLogoUrl()`** -- signal-based reactive logo URL builder
14
+ - **`logoUrl` pipe** -- template pipe for domain-to-URL conversion
15
+ - **Zero CSS dependencies** -- inline styles only, no Angular Material required
16
+ - **Standalone components** -- no `NgModule` needed, import directly
17
+ - **Signal inputs** -- modern Angular patterns with `input()` and `output()`
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ # pnpm (recommended)
23
+ pnpm add @quikturn/logos-angular @quikturn/logos
24
+
25
+ # npm
26
+ npm install @quikturn/logos-angular @quikturn/logos
27
+ ```
28
+
29
+ **Peer dependencies:** Angular `>= 17`, `@quikturn/logos >= 0.1.0`
30
+
31
+ ## Quick Start
32
+
33
+ ### 1. Provide your token at the application level
34
+
35
+ ```typescript
36
+ // app.config.ts
37
+ import { ApplicationConfig } from "@angular/core";
38
+ import { provideQuikturnLogos } from "@quikturn/logos-angular";
39
+
40
+ export const appConfig: ApplicationConfig = {
41
+ providers: [
42
+ provideQuikturnLogos({ token: "qt_your_publishable_key" }),
43
+ ],
44
+ };
45
+ ```
46
+
47
+ ### 2. Import and use components
48
+
49
+ ```typescript
50
+ import { Component } from "@angular/core";
51
+ import { QuikturnLogoComponent, QuikturnLogoCarouselComponent } from "@quikturn/logos-angular";
52
+
53
+ @Component({
54
+ selector: "app-example",
55
+ standalone: true,
56
+ imports: [QuikturnLogoComponent, QuikturnLogoCarouselComponent],
57
+ template: `
58
+ <!-- Single logo -->
59
+ <quikturn-logo domain="github.com" [size]="64" />
60
+
61
+ <!-- Infinite scrolling carousel -->
62
+ <quikturn-logo-carousel
63
+ [domains]="['github.com', 'stripe.com', 'vercel.com', 'figma.com']"
64
+ [speed]="120"
65
+ [fadeOut]="true"
66
+ [pauseOnHover]="true"
67
+ />
68
+ `,
69
+ })
70
+ export class ExampleComponent {}
71
+ ```
72
+
73
+ All components, pipes, and signal functions automatically inherit the token from `provideQuikturnLogos()`.
74
+
75
+ ## API Reference
76
+
77
+ ### `provideQuikturnLogos(config)`
78
+
79
+ Provides Quikturn configuration to the Angular DI system. Call this in your `ApplicationConfig` or a component's `providers` array.
80
+
81
+ ```typescript
82
+ provideQuikturnLogos({ token: "qt_abc123", baseUrl: "https://custom.api" })
83
+ ```
84
+
85
+ | Option | Type | Required | Description |
86
+ |--------|------|----------|-------------|
87
+ | `token` | `string` | yes | Publishable API key (`qt_`/`pk_` prefix) |
88
+ | `baseUrl` | `string` | no | Override the Quikturn API base URL |
89
+
90
+ ---
91
+
92
+ ### `<quikturn-logo>`
93
+
94
+ Renders a single logo `<img>`. Optionally wraps in an `<a>` tag when `href` is provided. Validates `href` to reject `javascript:` and `data:` protocols.
95
+
96
+ ```html
97
+ <quikturn-logo
98
+ domain="stripe.com"
99
+ [size]="128"
100
+ format="webp"
101
+ [greyscale]="true"
102
+ theme="dark"
103
+ alt="Stripe"
104
+ href="https://stripe.com"
105
+ loading="lazy"
106
+ class="my-logo"
107
+ />
108
+ ```
109
+
110
+ #### Inputs
111
+
112
+ | Input | Type | Default | Description |
113
+ |-------|------|---------|-------------|
114
+ | `domain` | `string` | **required** | Domain to fetch logo for |
115
+ | `token` | `string` | from provider | Publishable API key |
116
+ | `baseUrl` | `string` | from provider | API base URL override |
117
+ | `size` | `number` | -- | Logo width in pixels |
118
+ | `format` | `string` | -- | `"png"`, `"jpeg"`, `"webp"`, `"avif"`, or MIME type |
119
+ | `greyscale` | `boolean` | -- | Greyscale transformation |
120
+ | `theme` | `string` | -- | `"light"` or `"dark"` |
121
+ | `alt` | `string` | `"<domain> logo"` | Image alt text |
122
+ | `href` | `string` | -- | Wraps the image in a link |
123
+ | `loading` | `"lazy" \| "eager"` | `"lazy"` | Native image loading strategy |
124
+ | `class` | `string` | -- | CSS class on wrapper element |
125
+
126
+ #### Outputs
127
+
128
+ | Output | Type | Description |
129
+ |--------|------|-------------|
130
+ | `imgError` | `Event` | Emitted when the logo image fails to load |
131
+ | `imgLoad` | `Event` | Emitted when the logo image loads successfully |
132
+
133
+ ---
134
+
135
+ ### `<quikturn-logo-carousel>`
136
+
137
+ Infinite scrolling logo ticker powered by `requestAnimationFrame`. Supports horizontal (left/right) and vertical (up/down) scrolling, hover-based speed changes, fade overlays, and per-logo customization.
138
+
139
+ ```html
140
+ <quikturn-logo-carousel
141
+ [domains]="['github.com', 'stripe.com', 'vercel.com', 'figma.com']"
142
+ [speed]="120"
143
+ direction="left"
144
+ [logoHeight]="28"
145
+ [gap]="48"
146
+ [fadeOut]="true"
147
+ fadeOutColor="#f5f5f5"
148
+ [pauseOnHover]="true"
149
+ [scaleOnHover]="true"
150
+ width="100%"
151
+ />
152
+ ```
153
+
154
+ #### Using `logos` for per-logo configuration
155
+
156
+ ```html
157
+ <quikturn-logo-carousel
158
+ [logos]="[
159
+ { domain: 'github.com', href: 'https://github.com', alt: 'GitHub' },
160
+ { domain: 'stripe.com', size: 256, greyscale: true },
161
+ { domain: 'vercel.com', theme: 'dark' }
162
+ ]"
163
+ />
164
+ ```
165
+
166
+ #### Inputs
167
+
168
+ | Input | Type | Default | Description |
169
+ |-------|------|---------|-------------|
170
+ | `domains` | `string[]` | -- | Simple list of domains (use this or `logos`) |
171
+ | `logos` | `LogoConfig[]` | -- | Per-logo configuration objects (use this or `domains`) |
172
+ | `token` | `string` | from provider | Publishable API key |
173
+ | `baseUrl` | `string` | from provider | API base URL override |
174
+ | `speed` | `number` | `120` | Scroll speed in pixels per second |
175
+ | `direction` | `"left" \| "right" \| "up" \| "down"` | `"left"` | Scroll direction |
176
+ | `pauseOnHover` | `boolean` | -- | Pause scrolling on mouse hover |
177
+ | `hoverSpeed` | `number` | -- | Custom speed during hover (`0` = pause, overrides `pauseOnHover`) |
178
+ | `logoHeight` | `number` | `28` | Height of each logo image in pixels |
179
+ | `gap` | `number` | `32` | Gap between logos in pixels |
180
+ | `width` | `number \| string` | `"100%"` | Container width (`600`, `"80%"`, etc.) |
181
+ | `fadeOut` | `boolean` | `false` | Show gradient fade overlays at edges |
182
+ | `fadeOutColor` | `string` | `"#ffffff"` | Fade overlay color (match your background) |
183
+ | `scaleOnHover` | `boolean` | `false` | Scale logos on individual hover |
184
+ | `logoSize` | `number` | -- | Default image fetch width for all logos |
185
+ | `logoFormat` | `string` | -- | Default image format for all logos |
186
+ | `logoGreyscale` | `boolean` | -- | Default greyscale setting for all logos |
187
+ | `logoTheme` | `string` | -- | Default theme for all logos |
188
+ | `class` | `string` | -- | CSS class on root container |
189
+ | `ariaLabel` | `string` | `"Company logos"` | Accessible label for the region |
190
+
191
+ #### `LogoConfig`
192
+
193
+ Used in the `logos` array for per-logo customization:
194
+
195
+ ```typescript
196
+ interface LogoConfig {
197
+ domain: string; // Required
198
+ href?: string; // Wrap in link
199
+ alt?: string; // Alt text override
200
+ size?: number; // Per-logo image width
201
+ format?: string; // Per-logo format
202
+ greyscale?: boolean; // Per-logo greyscale
203
+ theme?: "light" | "dark";
204
+ }
205
+ ```
206
+
207
+ ---
208
+
209
+ ### `<quikturn-logo-grid>`
210
+
211
+ Responsive CSS grid of logos with optional custom templates.
212
+
213
+ ```html
214
+ <quikturn-logo-grid
215
+ [domains]="['github.com', 'stripe.com', 'vercel.com', 'figma.com']"
216
+ [columns]="4"
217
+ [gap]="24"
218
+ ariaLabel="Our partners"
219
+ />
220
+ ```
221
+
222
+ #### Custom template
223
+
224
+ ```html
225
+ <quikturn-logo-grid [domains]="companies">
226
+ <ng-template #renderItem let-logo let-i="index">
227
+ <div class="custom-card">
228
+ <img [src]="logo.url" [alt]="logo.alt" />
229
+ <span>{{ logo.domain }}</span>
230
+ </div>
231
+ </ng-template>
232
+ </quikturn-logo-grid>
233
+ ```
234
+
235
+ #### Inputs
236
+
237
+ | Input | Type | Default | Description |
238
+ |-------|------|---------|-------------|
239
+ | `domains` | `string[]` | -- | Simple list of domains (use this or `logos`) |
240
+ | `logos` | `LogoConfig[]` | -- | Per-logo configuration objects |
241
+ | `token` | `string` | from provider | Publishable API key |
242
+ | `baseUrl` | `string` | from provider | API base URL override |
243
+ | `columns` | `number` | `4` | Number of grid columns |
244
+ | `gap` | `number` | `24` | Grid gap in pixels |
245
+ | `logoSize` | `number` | -- | Default image fetch width |
246
+ | `logoFormat` | `string` | -- | Default image format |
247
+ | `logoGreyscale` | `boolean` | -- | Default greyscale |
248
+ | `logoTheme` | `string` | -- | Default theme |
249
+ | `class` | `string` | -- | CSS class on grid container |
250
+ | `ariaLabel` | `string` | `"Company logos"` | Accessible label for the region |
251
+
252
+ ---
253
+
254
+ ### `logoUrl` Pipe
255
+
256
+ Transform a domain string into a logo URL directly in templates.
257
+
258
+ ```typescript
259
+ import { Component } from "@angular/core";
260
+ import { LogoUrlPipe } from "@quikturn/logos-angular";
261
+
262
+ @Component({
263
+ selector: "app-pipe-demo",
264
+ standalone: true,
265
+ imports: [LogoUrlPipe],
266
+ template: `
267
+ <img [src]="'github.com' | logoUrl" alt="GitHub logo" />
268
+ <img [src]="'google.com' | logoUrl: { size: 64, format: 'webp' }" alt="Google logo" />
269
+ `,
270
+ })
271
+ export class PipeDemoComponent {}
272
+ ```
273
+
274
+ The pipe reads the token from the `QUIKTURN_CONFIG` injection token. You can override per-use:
275
+
276
+ ```html
277
+ <img [src]="'github.com' | logoUrl: { token: 'qt_other_token' }" alt="GitHub" />
278
+ ```
279
+
280
+ ---
281
+
282
+ ### `injectLogoUrl(options)`
283
+
284
+ Signal-based helper that builds a reactive logo URL. Must be called within an Angular injection context. The returned signal recomputes automatically whenever any input signal changes.
285
+
286
+ ```typescript
287
+ import { Component, signal } from "@angular/core";
288
+ import { injectLogoUrl } from "@quikturn/logos-angular";
289
+
290
+ @Component({
291
+ selector: "app-signal-demo",
292
+ standalone: true,
293
+ template: `<img [src]="logoSrc()" alt="Logo" />`,
294
+ })
295
+ export class SignalDemoComponent {
296
+ domain = signal("github.com");
297
+
298
+ logoSrc = injectLogoUrl({
299
+ domain: () => this.domain(),
300
+ size: () => 128,
301
+ format: () => "webp",
302
+ });
303
+ }
304
+ ```
305
+
306
+ | Option | Type | Description |
307
+ |--------|------|-------------|
308
+ | `domain` | `() => string` | Reactive getter for the domain (required) |
309
+ | `token` | `() => string \| undefined` | Override the provider token |
310
+ | `baseUrl` | `() => string \| undefined` | Override the provider base URL |
311
+ | `size` | `() => number \| undefined` | Logo width in pixels |
312
+ | `format` | `() => string \| undefined` | Output format |
313
+ | `greyscale` | `() => boolean \| undefined` | Greyscale transformation |
314
+ | `theme` | `() => string \| undefined` | Theme optimization |
315
+
316
+ **Returns:** `Signal<string>` -- computed signal with the full logo URL
317
+
318
+ ---
319
+
320
+ ## Types
321
+
322
+ All types are exported for use in your application:
323
+
324
+ ```typescript
325
+ import type {
326
+ QuikturnConfig,
327
+ LogoOptions,
328
+ LogoConfig,
329
+ ResolvedLogo,
330
+ SupportedOutputFormat,
331
+ FormatShorthand,
332
+ ThemeOption,
333
+ } from "@quikturn/logos-angular";
334
+ ```
335
+
336
+ | Type | Description |
337
+ |------|-------------|
338
+ | `QuikturnConfig` | Configuration for `provideQuikturnLogos()` (`token`, optional `baseUrl`) |
339
+ | `LogoOptions` | Options for logo generation (`size`, `format`, `greyscale`, `theme`) |
340
+ | `LogoConfig` | Per-logo config extending `LogoOptions` with `domain`, `href`, `alt` |
341
+ | `ResolvedLogo` | Internal resolved logo with pre-built `url`, `domain`, `alt`, `href` |
342
+ | `SupportedOutputFormat` | Full format strings (re-exported from `@quikturn/logos`) |
343
+ | `FormatShorthand` | Short format aliases (re-exported from `@quikturn/logos`) |
344
+ | `ThemeOption` | `"light" \| "dark"` (re-exported from `@quikturn/logos`) |
345
+
346
+ ---
347
+
348
+ ## API Summary
349
+
350
+ | Export | Kind | Description |
351
+ |--------|------|-------------|
352
+ | `provideQuikturnLogos()` | Function | Provide config to Angular DI |
353
+ | `QUIKTURN_CONFIG` | InjectionToken | Access config directly via `inject()` |
354
+ | `injectLogoUrl()` | Function | Signal-based reactive logo URL builder |
355
+ | `LogoUrlPipe` | Pipe | Template pipe for domain-to-URL conversion |
356
+ | `QuikturnLogoComponent` | Component | Single logo image |
357
+ | `QuikturnLogoGridComponent` | Component | Grid layout of logos |
358
+ | `QuikturnLogoCarouselComponent` | Component | Infinite scrolling carousel |
359
+
360
+ ---
361
+
362
+ ## Examples
363
+
364
+ ### Logo Wall (Marketing Page)
365
+
366
+ ```typescript
367
+ @Component({
368
+ selector: "app-logo-wall",
369
+ standalone: true,
370
+ imports: [QuikturnLogoCarouselComponent],
371
+ template: `
372
+ <h2>Trusted by industry leaders</h2>
373
+ <quikturn-logo-carousel
374
+ [domains]="partners"
375
+ [speed]="80"
376
+ [logoHeight]="32"
377
+ [gap]="64"
378
+ [fadeOut]="true"
379
+ [pauseOnHover]="true"
380
+ />
381
+ `,
382
+ })
383
+ export class LogoWallComponent {
384
+ partners = [
385
+ "github.com", "stripe.com", "vercel.com", "figma.com",
386
+ "linear.app", "notion.so", "slack.com", "discord.com",
387
+ ];
388
+ }
389
+ ```
390
+
391
+ ### Partner Grid with Links
392
+
393
+ ```typescript
394
+ @Component({
395
+ selector: "app-partner-grid",
396
+ standalone: true,
397
+ imports: [QuikturnLogoGridComponent],
398
+ template: `
399
+ <quikturn-logo-grid [logos]="partners" [columns]="2" [gap]="32" />
400
+ `,
401
+ })
402
+ export class PartnerGridComponent {
403
+ partners: LogoConfig[] = [
404
+ { domain: "github.com", href: "https://github.com", alt: "GitHub" },
405
+ { domain: "stripe.com", href: "https://stripe.com", alt: "Stripe" },
406
+ { domain: "vercel.com", href: "https://vercel.com", alt: "Vercel" },
407
+ { domain: "figma.com", href: "https://figma.com", alt: "Figma" },
408
+ ];
409
+ }
410
+ ```
411
+
412
+ ### Vertical Carousel
413
+
414
+ ```html
415
+ <div style="height: 240px">
416
+ <quikturn-logo-carousel
417
+ [domains]="['github.com', 'stripe.com', 'vercel.com']"
418
+ direction="up"
419
+ [speed]="60"
420
+ [logoHeight]="24"
421
+ />
422
+ </div>
423
+ ```
424
+
425
+ ### Signal-Based Reactive URL
426
+
427
+ ```typescript
428
+ @Component({
429
+ selector: "app-reactive",
430
+ standalone: true,
431
+ template: `
432
+ <input [(ngModel)]="domain" placeholder="Enter a domain" />
433
+ <img [src]="logoSrc()" [alt]="domain() + ' logo'" />
434
+ `,
435
+ })
436
+ export class ReactiveComponent {
437
+ domain = signal("github.com");
438
+ logoSrc = injectLogoUrl({
439
+ domain: () => this.domain(),
440
+ size: () => 256,
441
+ format: () => "webp",
442
+ });
443
+ }
444
+ ```
445
+
446
+ ## Resources
447
+
448
+ - **[Quikturn website](https://getquikturn.io)** -- sign up, manage keys, explore the API
449
+ - **[Dashboard](https://getquikturn.io/dashboard)** -- usage analytics, key management, plan upgrades
450
+ - **[Pricing](https://getquikturn.io/pricing)** -- free tier, pro, and enterprise plans
451
+ - **[Core SDK docs](https://www.npmjs.com/package/@quikturn/logos)** -- `@quikturn/logos` URL builder, browser client, server client
452
+
453
+ ## License
454
+
455
+ MIT -- built by [Quikturn](https://getquikturn.io)
package/dist/index.cjs ADDED
@@ -0,0 +1,206 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("@angular/core"),g=require("@quikturn/logos"),O=require("@angular/common"),u=new t.InjectionToken("QUIKTURN_CONFIG");function x(s){return t.makeEnvironmentProviders([{provide:u,useValue:s}])}function w(s){t.assertInInjectionContext(w);const e=t.inject(u,{optional:!0});return t.computed(()=>{var o,r,a,l,c,d;const i=((o=s.token)==null?void 0:o.call(s))??(e==null?void 0:e.token),n=((r=s.baseUrl)==null?void 0:r.call(s))??(e==null?void 0:e.baseUrl);return g.logoUrl(s.domain(),{token:i,baseUrl:n,size:(a=s.size)==null?void 0:a.call(s),format:(l=s.format)==null?void 0:l.call(s),greyscale:(c=s.greyscale)==null?void 0:c.call(s),theme:(d=s.theme)==null?void 0:d.call(s)})})}var U=Object.getOwnPropertyDescriptor,L=(s,e,i,n)=>{for(var o=n>1?void 0:n?U(e,i):e,r=s.length-1,a;r>=0;r--)(a=s[r])&&(o=a(o)||o);return o};exports.LogoUrlPipe=class{constructor(){this.config=t.inject(u,{optional:!0})}transform(e,i){var r,a;const n=(i==null?void 0:i.token)??((r=this.config)==null?void 0:r.token),o=(i==null?void 0:i.baseUrl)??((a=this.config)==null?void 0:a.baseUrl);return g.logoUrl(e,{token:n,baseUrl:o,size:i==null?void 0:i.size,format:i==null?void 0:i.format,greyscale:i==null?void 0:i.greyscale,theme:i==null?void 0:i.theme})}};exports.LogoUrlPipe=L([t.Pipe({name:"logoUrl",standalone:!0})],exports.LogoUrlPipe);const k=new Set;function y(s){if(typeof window>"u"||!s||s.startsWith("sk_")||k.has(s))return;k.add(s);const e=new Image;e.src=`${g.BASE_URL}/_beacon?token=${s}&page=${encodeURIComponent(location.href)}`}function b(s){try{const e=new URL(s,"https://placeholder.invalid");return e.protocol==="https:"||e.protocol==="http:"}catch{return!1}}var z=Object.getOwnPropertyDescriptor,I=(s,e,i,n)=>{for(var o=n>1?void 0:n?z(e,i):e,r=s.length-1,a;r>=0;r--)(a=s[r])&&(o=a(o)||o);return o};exports.QuikturnLogoComponent=class{constructor(){this.config=t.inject(u,{optional:!0}),this.domain=t.input.required(),this.token=t.input(void 0),this.baseUrl=t.input(void 0),this.size=t.input(void 0),this.format=t.input(void 0),this.greyscale=t.input(void 0),this.theme=t.input(void 0),this.alt=t.input(void 0),this.href=t.input(void 0),this.cssClass=t.input(void 0,{alias:"class"}),this.loading=t.input("lazy"),this.imgError=t.output(),this.imgLoad=t.output(),this.effectiveToken=t.computed(()=>{var e;return this.token()??((e=this.config)==null?void 0:e.token)??""}),this.effectiveBaseUrl=t.computed(()=>{var e;return this.baseUrl()??((e=this.config)==null?void 0:e.baseUrl)}),this.safeHref=t.computed(()=>{const e=this.href();return e&&b(e)?e:void 0}),this.src=t.computed(()=>g.logoUrl(this.domain(),{token:this.effectiveToken()||void 0,size:this.size(),format:this.format(),greyscale:this.greyscale(),theme:this.theme(),baseUrl:this.effectiveBaseUrl()})),this.altText=t.computed(()=>this.alt()??`${this.domain()} logo`)}ngOnInit(){const e=this.effectiveToken();e&&y(e)}};exports.QuikturnLogoComponent=I([t.Component({selector:"quikturn-logo",standalone:!0,template:`
2
+ @if (safeHref()) {
3
+ <a [href]="safeHref()" target="_blank" rel="noopener noreferrer" [class]="cssClass()">
4
+ <img [src]="src()" [alt]="altText()" [attr.loading]="loading()" (error)="imgError.emit($event)" (load)="imgLoad.emit($event)" />
5
+ </a>
6
+ } @else if (cssClass()) {
7
+ <span [class]="cssClass()">
8
+ <img [src]="src()" [alt]="altText()" [attr.loading]="loading()" (error)="imgError.emit($event)" (load)="imgLoad.emit($event)" />
9
+ </span>
10
+ } @else {
11
+ <img [src]="src()" [alt]="altText()" [attr.loading]="loading()" (error)="imgError.emit($event)" (load)="imgLoad.emit($event)" />
12
+ }
13
+ `})],exports.QuikturnLogoComponent);var _=Object.getOwnPropertyDescriptor,T=(s,e,i,n)=>{for(var o=n>1?void 0:n?_(e,i):e,r=s.length-1,a;r>=0;r--)(a=s[r])&&(o=a(o)||o);return o};exports.QuikturnLogoGridComponent=class{constructor(){this.config=t.inject(u,{optional:!0}),this.domains=t.input(void 0),this.logos=t.input(void 0),this.token=t.input(void 0),this.baseUrl=t.input(void 0),this.columns=t.input(4),this.gap=t.input(24),this.logoSize=t.input(void 0),this.logoFormat=t.input(void 0),this.logoGreyscale=t.input(void 0),this.logoTheme=t.input(void 0),this.cssClass=t.input(void 0,{alias:"class"}),this.ariaLabel=t.input("Company logos"),this.customTemplate=t.contentChild("renderItem"),this.effectiveToken=t.computed(()=>{var e;return this.token()??((e=this.config)==null?void 0:e.token)??""}),this.effectiveBaseUrl=t.computed(()=>{var e;return this.baseUrl()??((e=this.config)==null?void 0:e.baseUrl)}),this.resolvedLogos=t.computed(()=>(this.logos()??(this.domains()??[]).map(i=>({domain:i}))).map(i=>({domain:i.domain,alt:i.alt??`${i.domain} logo`,href:i.href&&b(i.href)?i.href:void 0,url:g.logoUrl(i.domain,{token:this.effectiveToken()||void 0,size:i.size??this.logoSize(),format:i.format??this.logoFormat(),greyscale:i.greyscale??this.logoGreyscale(),theme:i.theme??this.logoTheme(),baseUrl:this.effectiveBaseUrl()})})))}ngOnInit(){const e=this.effectiveToken();e&&y(e)}};exports.QuikturnLogoGridComponent=T([t.Component({selector:"quikturn-logo-grid",standalone:!0,imports:[O.CommonModule],template:`
14
+ <div
15
+ role="region"
16
+ [attr.aria-label]="ariaLabel()"
17
+ [class]="cssClass()"
18
+ [style.display]="'grid'"
19
+ [style.grid-template-columns]="'repeat(' + columns() + ', 1fr)'"
20
+ [style.gap.px]="gap()"
21
+ [style.align-items]="'center'"
22
+ [style.justify-items]="'center'"
23
+ >
24
+ @for (logo of resolvedLogos(); track logo.domain; let i = $index) {
25
+ @if (customTemplate()) {
26
+ <ng-container
27
+ [ngTemplateOutlet]="customTemplate()!"
28
+ [ngTemplateOutletContext]="{ $implicit: logo, index: i }"
29
+ ></ng-container>
30
+ } @else {
31
+ <div style="display: flex; align-items: center; justify-content: center;">
32
+ @if (logo.href) {
33
+ <a
34
+ [href]="logo.href"
35
+ target="_blank"
36
+ rel="noopener noreferrer"
37
+ [attr.aria-label]="logo.alt"
38
+ >
39
+ <img
40
+ [src]="logo.url"
41
+ [alt]="logo.alt"
42
+ loading="lazy"
43
+ style="max-width: 100%; height: auto; display: block;"
44
+ />
45
+ </a>
46
+ } @else {
47
+ <img
48
+ [src]="logo.url"
49
+ [alt]="logo.alt"
50
+ loading="lazy"
51
+ style="max-width: 100%; height: auto; display: block;"
52
+ />
53
+ }
54
+ </div>
55
+ }
56
+ }
57
+ </div>
58
+ `})],exports.QuikturnLogoGridComponent);var E=Object.defineProperty,M=Object.getOwnPropertyDescriptor,C=(s,e,i,n)=>{for(var o=n>1?void 0:n?M(e,i):e,r=s.length-1,a;r>=0;r--)(a=s[r])&&(o=(n?a(e,i,o):a(o))||o);return n&&o&&E(e,i,o),o};const f={SMOOTH_TAU:.25,MIN_COPIES:2,COPY_HEADROOM:2};exports.QuikturnLogoCarouselComponent=class{constructor(){this.config=t.inject(u,{optional:!0}),this.domains=t.input(void 0),this.logos=t.input(void 0),this.token=t.input(void 0),this.baseUrl=t.input(void 0),this.speed=t.input(120),this.direction=t.input("left"),this.pauseOnHover=t.input(void 0),this.hoverSpeed=t.input(void 0),this.logoHeight=t.input(28),this.gap=t.input(32),this.width=t.input("100%"),this.fadeOut=t.input(!1),this.fadeOutColor=t.input("#ffffff"),this.scaleOnHover=t.input(!1),this.logoSize=t.input(void 0),this.logoFormat=t.input(void 0),this.logoGreyscale=t.input(void 0),this.logoTheme=t.input(void 0),this.cssClass=t.input(void 0,{alias:"class"}),this.ariaLabel=t.input("Company logos"),this.seqWidth=t.signal(0),this.seqHeight=t.signal(0),this.copyCount=t.signal(f.MIN_COPIES),this.isHovered=t.signal(!1),this.animationFrameId=null,this.currentVelocity=0,this.offset=0,this.resizeObserver=null,this.handleResize=()=>this.updateDimensions(),this.effectiveToken=t.computed(()=>{var e;return this.token()??((e=this.config)==null?void 0:e.token)??""}),this.effectiveBaseUrl=t.computed(()=>{var e;return this.baseUrl()??((e=this.config)==null?void 0:e.baseUrl)}),this.isVertical=t.computed(()=>this.direction()==="up"||this.direction()==="down"),this.resolvedLogos=t.computed(()=>(this.logos()??(this.domains()??[]).map(i=>({domain:i}))).map(i=>({domain:i.domain,alt:i.alt??`${i.domain} logo`,href:i.href,url:g.logoUrl(i.domain,{token:this.effectiveToken()||void 0,size:i.size??this.logoSize(),format:i.format??this.logoFormat(),greyscale:i.greyscale??this.logoGreyscale(),theme:i.theme??this.logoTheme(),baseUrl:this.effectiveBaseUrl()})}))),this.cssWidth=t.computed(()=>{const e=this.width();return typeof e=="number"?`${e}px`:e??"100%"}),this.copyIndices=t.computed(()=>Array.from({length:this.copyCount()},(e,i)=>i)),this.targetVelocity=t.computed(()=>{const e=Math.abs(this.speed()),i=this.isVertical()?this.direction()==="up"?1:-1:this.direction()==="left"?1:-1,n=this.speed()<0?-1:1;return e*i*n}),this.effectiveHoverSpeed=t.computed(()=>{const e=this.hoverSpeed();if(e!==void 0)return e;if(this.pauseOnHover()===!0)return 0})}ngOnInit(){const e=this.effectiveToken();e&&y(e)}ngAfterViewInit(){var i;this.updateDimensions(),this.trackImageLoads(),this.startAnimation();const e=(i=this.containerRef)==null?void 0:i.nativeElement;e&&typeof window<"u"&&(window.ResizeObserver?(this.resizeObserver=new ResizeObserver(()=>this.updateDimensions()),this.resizeObserver.observe(e)):window.addEventListener("resize",this.handleResize))}ngOnDestroy(){var e;this.stopAnimation(),(e=this.resizeObserver)==null||e.disconnect(),this.resizeObserver=null,typeof window<"u"&&window.removeEventListener("resize",this.handleResize)}handleMouseEnter(){this.effectiveHoverSpeed()!==void 0&&this.isHovered.set(!0)}handleMouseLeave(){this.effectiveHoverSpeed()!==void 0&&this.isHovered.set(!1)}updateDimensions(){var l,c,d;const e=(l=this.containerRef)==null?void 0:l.nativeElement,i=(c=this.trackRef)==null?void 0:c.nativeElement;if(!e||!i)return;const n=i.querySelector("ul");if(!n)return;const o=n.getBoundingClientRect(),r=o.width,a=o.height;if(this.isVertical()){const h=((d=e.parentElement)==null?void 0:d.clientHeight)??0;if(h>0&&(e.style.height=`${Math.ceil(h)}px`),a>0){this.seqHeight.set(Math.ceil(a));const m=e.clientHeight||h||a,v=Math.ceil(m/a)+f.COPY_HEADROOM;this.copyCount.set(Math.max(f.MIN_COPIES,v))}}else if(r>0){this.seqWidth.set(Math.ceil(r));const h=e.clientWidth||0,m=Math.ceil(h/r)+f.COPY_HEADROOM;this.copyCount.set(Math.max(f.MIN_COPIES,m))}}trackImageLoads(){var r;const e=(r=this.trackRef)==null?void 0:r.nativeElement;if(!e)return;const i=e.querySelectorAll("ul:first-child img");if(i.length===0)return;let n=i.length;const o=()=>{n--,n===0&&this.updateDimensions()};i.forEach(a=>{const l=a;l.complete?o():(l.addEventListener("load",o,{once:!0}),l.addEventListener("error",o,{once:!0}))})}startAnimation(){var r,a;const e=(r=this.trackRef)==null?void 0:r.nativeElement;if(!e)return;if(typeof window<"u"&&((a=window.matchMedia)==null?void 0:a.call(window,"(prefers-reduced-motion: reduce)").matches)){e.style.transform="translate3d(0, 0, 0)";return}let n=null;this.currentVelocity=this.targetVelocity();const o=l=>{if(n===null){n=l,this.animationFrameId=requestAnimationFrame(o);return}const c=Math.min(Math.max(0,(l-n)/1e3),.1);n=l;const d=this.effectiveHoverSpeed(),h=this.isHovered()&&d!==void 0?d:this.targetVelocity(),m=f.SMOOTH_TAU,v=1-Math.exp(-c/m);this.currentVelocity+=(h-this.currentVelocity)*v,this.offset+=this.currentVelocity*c;const p=this.isVertical()?this.seqHeight():this.seqWidth();p>0&&(this.offset=(this.offset%p+p)%p),this.isVertical()?e.style.transform=`translate3d(0, ${-this.offset}px, 0)`:e.style.transform=`translate3d(${-this.offset}px, 0, 0)`,this.animationFrameId=requestAnimationFrame(o)};this.animationFrameId=requestAnimationFrame(o)}stopAnimation(){this.animationFrameId!==null&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null)}};C([t.ViewChild("container")],exports.QuikturnLogoCarouselComponent.prototype,"containerRef",2);C([t.ViewChild("track")],exports.QuikturnLogoCarouselComponent.prototype,"trackRef",2);exports.QuikturnLogoCarouselComponent=C([t.Component({selector:"quikturn-logo-carousel",standalone:!0,imports:[O.CommonModule],template:`
59
+ <div
60
+ #container
61
+ role="region"
62
+ [attr.aria-label]="ariaLabel()"
63
+ [class]="cssClass()"
64
+ [style.position]="'relative'"
65
+ [style.overflow]="'hidden'"
66
+ [style.width]="isVertical() ? undefined : cssWidth()"
67
+ [style.height]="isVertical() ? '100%' : undefined"
68
+ >
69
+ @if (fadeOut()) {
70
+ @if (isVertical()) {
71
+ <div
72
+ aria-hidden="true"
73
+ data-testid="fade-overlay"
74
+ [style.position]="'absolute'"
75
+ [style.pointer-events]="'none'"
76
+ [style.z-index]="1"
77
+ [style.top]="'0'"
78
+ [style.left]="'0'"
79
+ [style.right]="'0'"
80
+ [style.height]="'clamp(24px, 8%, 120px)'"
81
+ [style.background]="
82
+ 'linear-gradient(to bottom, ' +
83
+ fadeOutColor() +
84
+ ' 0%, transparent 100%)'
85
+ "
86
+ ></div>
87
+ <div
88
+ aria-hidden="true"
89
+ data-testid="fade-overlay"
90
+ [style.position]="'absolute'"
91
+ [style.pointer-events]="'none'"
92
+ [style.z-index]="1"
93
+ [style.bottom]="'0'"
94
+ [style.left]="'0'"
95
+ [style.right]="'0'"
96
+ [style.height]="'clamp(24px, 8%, 120px)'"
97
+ [style.background]="
98
+ 'linear-gradient(to top, ' +
99
+ fadeOutColor() +
100
+ ' 0%, transparent 100%)'
101
+ "
102
+ ></div>
103
+ } @else {
104
+ <div
105
+ aria-hidden="true"
106
+ data-testid="fade-overlay"
107
+ [style.position]="'absolute'"
108
+ [style.pointer-events]="'none'"
109
+ [style.z-index]="1"
110
+ [style.top]="'0'"
111
+ [style.bottom]="'0'"
112
+ [style.left]="'0'"
113
+ [style.width]="'clamp(24px, 8%, 120px)'"
114
+ [style.background]="
115
+ 'linear-gradient(to right, ' +
116
+ fadeOutColor() +
117
+ ' 0%, transparent 100%)'
118
+ "
119
+ ></div>
120
+ <div
121
+ aria-hidden="true"
122
+ data-testid="fade-overlay"
123
+ [style.position]="'absolute'"
124
+ [style.pointer-events]="'none'"
125
+ [style.z-index]="1"
126
+ [style.top]="'0'"
127
+ [style.bottom]="'0'"
128
+ [style.right]="'0'"
129
+ [style.width]="'clamp(24px, 8%, 120px)'"
130
+ [style.background]="
131
+ 'linear-gradient(to left, ' +
132
+ fadeOutColor() +
133
+ ' 0%, transparent 100%)'
134
+ "
135
+ ></div>
136
+ }
137
+ }
138
+
139
+ <div
140
+ #track
141
+ [style.display]="'flex'"
142
+ [style.flex-direction]="isVertical() ? 'column' : 'row'"
143
+ [style.width]="isVertical() ? '100%' : 'max-content'"
144
+ [style.will-change]="'transform'"
145
+ [style.user-select]="'none'"
146
+ [style.position]="'relative'"
147
+ [style.z-index]="'0'"
148
+ (mouseenter)="handleMouseEnter()"
149
+ (mouseleave)="handleMouseLeave()"
150
+ >
151
+ @for (ci of copyIndices(); track ci) {
152
+ <ul
153
+ role="list"
154
+ [attr.aria-hidden]="ci > 0 ? 'true' : null"
155
+ [style.display]="'flex'"
156
+ [style.flex-direction]="isVertical() ? 'column' : 'row'"
157
+ [style.align-items]="'center'"
158
+ [style.list-style]="'none'"
159
+ [style.margin]="'0'"
160
+ [style.padding]="'0'"
161
+ >
162
+ @for (logo of resolvedLogos(); track logo.domain + '-' + ci + '-' + $index) {
163
+ <li
164
+ role="listitem"
165
+ [style.flex]="'none'"
166
+ [style.margin-right.px]="isVertical() ? 0 : gap()"
167
+ [style.margin-bottom.px]="isVertical() ? gap() : 0"
168
+ >
169
+ @if (logo.href) {
170
+ <a
171
+ [href]="logo.href"
172
+ target="_blank"
173
+ rel="noopener noreferrer"
174
+ [attr.aria-label]="logo.alt"
175
+ style="display: inline-flex; align-items: center; text-decoration: none"
176
+ >
177
+ <img
178
+ [src]="logo.url"
179
+ [alt]="logo.alt"
180
+ loading="lazy"
181
+ decoding="async"
182
+ [attr.draggable]="false"
183
+ [style.height.px]="logoHeight()"
184
+ style="width: auto; display: block; object-fit: contain"
185
+ />
186
+ </a>
187
+ } @else {
188
+ <span style="display: inline-flex; align-items: center">
189
+ <img
190
+ [src]="logo.url"
191
+ [alt]="logo.alt"
192
+ loading="lazy"
193
+ decoding="async"
194
+ [attr.draggable]="false"
195
+ [style.height.px]="logoHeight()"
196
+ style="width: auto; display: block; object-fit: contain"
197
+ />
198
+ </span>
199
+ }
200
+ </li>
201
+ }
202
+ </ul>
203
+ }
204
+ </div>
205
+ </div>
206
+ `})],exports.QuikturnLogoCarouselComponent);exports.QUIKTURN_CONFIG=u;exports.injectLogoUrl=w;exports.isValidHref=b;exports.provideQuikturnLogos=x;