Skip to content

Commit 22b11da

Browse files
feat: Add framework adapters and documentation (#22)
This commit introduces adapters for React, Vue, Angular, SolidJS, Svelte, and Web Components, along with comprehensive documentation. Co-authored-by: Cursor Agent <[email protected]>
1 parent ab8e42a commit 22b11da

File tree

10 files changed

+1715
-62
lines changed

10 files changed

+1715
-62
lines changed

ADAPTERS.md

Lines changed: 423 additions & 0 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,28 @@
1212
"types": "./dist/index.d.ts"
1313
},
1414
"./react": {
15-
"import": "./dist/react.js",
16-
"types": "./dist/react.d.ts"
15+
"import": "./dist/adapters/react.js",
16+
"types": "./dist/adapters/react.d.ts"
1717
},
1818
"./vue": {
19-
"import": "./dist/vue.js",
20-
"types": "./dist/vue.d.ts"
19+
"import": "./dist/adapters/vue.js",
20+
"types": "./dist/adapters/vue.d.ts"
21+
},
22+
"./angular": {
23+
"import": "./dist/adapters/angular.js",
24+
"types": "./dist/adapters/angular.d.ts"
25+
},
26+
"./solid": {
27+
"import": "./dist/adapters/solid.js",
28+
"types": "./dist/adapters/solid.d.ts"
29+
},
30+
"./svelte": {
31+
"import": "./dist/adapters/svelte.js",
32+
"types": "./dist/adapters/svelte.d.ts"
33+
},
34+
"./webcomponent": {
35+
"import": "./dist/adapters/webcomponent.js",
36+
"types": "./dist/adapters/webcomponent.d.ts"
2137
},
2238
"./style.css": "./dist/style.css"
2339
},
@@ -50,6 +66,11 @@
5066
"sports",
5167
"react",
5268
"vue",
69+
"angular",
70+
"solid",
71+
"solidjs",
72+
"svelte",
73+
"web-components",
5374
"typescript"
5475
],
5576
"author": "Erik Zettersten",
@@ -83,13 +104,25 @@
83104
"vue": "^3.5.12"
84105
},
85106
"peerDependencies": {
86-
"react": ">=16.8.0",
107+
"@angular/core": ">=18.0.0",
108+
"react": ">=18.0.0",
109+
"solid-js": ">=1.8.0",
110+
"svelte": ">=4.0.0 || >=5.0.0",
87111
"vue": ">=3.0.0"
88112
},
89113
"peerDependenciesMeta": {
114+
"@angular/core": {
115+
"optional": true
116+
},
90117
"react": {
91118
"optional": true
92119
},
120+
"solid-js": {
121+
"optional": true
122+
},
123+
"svelte": {
124+
"optional": true
125+
},
93126
"vue": {
94127
"optional": true
95128
}

src/adapters/angular.ts

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import {
2+
Component,
3+
Input,
4+
Output,
5+
EventEmitter,
6+
OnInit,
7+
OnDestroy,
8+
OnChanges,
9+
SimpleChanges,
10+
ElementRef,
11+
ViewChild,
12+
ChangeDetectionStrategy,
13+
inject,
14+
effect,
15+
signal,
16+
} from '@angular/core';
17+
import { Gracket } from '../core/Gracket';
18+
import type { GracketOptions, TournamentData } from '../types';
19+
20+
/**
21+
* Angular 18+ Component wrapper for Gracket
22+
* Uses modern Angular features: signals, inject, standalone components
23+
*/
24+
@Component({
25+
selector: 'gracket',
26+
standalone: true,
27+
template: `
28+
@if (error()) {
29+
<div class="gracket-error" [style]="{ color: 'red', padding: '1rem' }">
30+
Error initializing Gracket: {{ error()?.message }}
31+
</div>
32+
} @else {
33+
<div #container [class]="className" [ngStyle]="style"></div>
34+
}
35+
`,
36+
changeDetection: ChangeDetectionStrategy.OnPush,
37+
})
38+
export class GracketComponent implements OnInit, OnDestroy, OnChanges {
39+
@ViewChild('container', { static: false }) containerRef?: ElementRef<HTMLDivElement>;
40+
41+
@Input({ required: true }) data!: TournamentData;
42+
@Input() options?: Omit<GracketOptions, 'src'>;
43+
@Input() className?: string;
44+
@Input() style?: Record<string, string>;
45+
46+
@Output() init = new EventEmitter<Gracket>();
47+
@Output() errorOccurred = new EventEmitter<Error>();
48+
@Output() dataUpdated = new EventEmitter<TournamentData>();
49+
50+
// Using signals for reactive state - Angular 18+ best practice
51+
protected error = signal<Error | null>(null);
52+
private gracketInstance = signal<Gracket | null>(null);
53+
private elementRef = inject(ElementRef);
54+
55+
ngOnInit(): void {
56+
// Initialize will happen after view init when container is available
57+
}
58+
59+
ngAfterViewInit(): void {
60+
this.initializeGracket();
61+
}
62+
63+
ngOnChanges(changes: SimpleChanges): void {
64+
if (changes['data'] && !changes['data'].firstChange) {
65+
this.updateData();
66+
}
67+
68+
if (changes['options'] && !changes['options'].firstChange) {
69+
this.reinitializeGracket();
70+
}
71+
}
72+
73+
ngOnDestroy(): void {
74+
this.destroy();
75+
}
76+
77+
private initializeGracket(): void {
78+
const container = this.containerRef?.nativeElement;
79+
if (!container || !this.data?.length) return;
80+
81+
try {
82+
const instance = new Gracket(container, {
83+
...this.options,
84+
src: this.data,
85+
});
86+
87+
this.gracketInstance.set(instance);
88+
this.error.set(null);
89+
this.init.emit(instance);
90+
} catch (err) {
91+
const error = err as Error;
92+
this.error.set(error);
93+
this.errorOccurred.emit(error);
94+
console.error('Gracket initialization error:', err);
95+
}
96+
}
97+
98+
private reinitializeGracket(): void {
99+
this.destroy();
100+
this.initializeGracket();
101+
}
102+
103+
private updateData(): void {
104+
const instance = this.gracketInstance();
105+
if (instance && this.data?.length) {
106+
try {
107+
instance.update(this.data);
108+
this.error.set(null);
109+
this.dataUpdated.emit(this.data);
110+
} catch (err) {
111+
const error = err as Error;
112+
this.error.set(error);
113+
this.errorOccurred.emit(error);
114+
}
115+
}
116+
}
117+
118+
/** Update a team's score */
119+
public updateScore(
120+
roundIndex: number,
121+
gameIndex: number,
122+
teamIndex: number,
123+
score: number
124+
): void {
125+
try {
126+
this.gracketInstance()?.updateScore(roundIndex, gameIndex, teamIndex, score);
127+
this.error.set(null);
128+
} catch (err) {
129+
this.error.set(err as Error);
130+
}
131+
}
132+
133+
/** Advance to next round */
134+
public advanceRound(fromRound?: number): TournamentData | undefined {
135+
try {
136+
const result = this.gracketInstance()?.advanceRound(fromRound);
137+
this.error.set(null);
138+
return result;
139+
} catch (err) {
140+
this.error.set(err as Error);
141+
return undefined;
142+
}
143+
}
144+
145+
/** Get the Gracket instance */
146+
public getInstance(): Gracket | null {
147+
return this.gracketInstance();
148+
}
149+
150+
/** Destroy the instance */
151+
public destroy(): void {
152+
const instance = this.gracketInstance();
153+
if (instance) {
154+
instance.destroy();
155+
this.gracketInstance.set(null);
156+
}
157+
}
158+
}
159+
160+
/**
161+
* Angular Service for programmatic Gracket control
162+
* Can be injected into components
163+
*/
164+
export class GracketService {
165+
private instances = new Map<string, Gracket>();
166+
167+
/** Create a new Gracket instance */
168+
create(
169+
id: string,
170+
container: HTMLElement,
171+
data: TournamentData,
172+
options?: GracketOptions
173+
): Gracket {
174+
// Destroy existing instance if any
175+
this.destroy(id);
176+
177+
const instance = new Gracket(container, {
178+
...options,
179+
src: data,
180+
});
181+
182+
this.instances.set(id, instance);
183+
return instance;
184+
}
185+
186+
/** Get an existing instance */
187+
get(id: string): Gracket | undefined {
188+
return this.instances.get(id);
189+
}
190+
191+
/** Update data for an instance */
192+
update(id: string, data: TournamentData): void {
193+
this.instances.get(id)?.update(data);
194+
}
195+
196+
/** Update score */
197+
updateScore(
198+
id: string,
199+
roundIndex: number,
200+
gameIndex: number,
201+
teamIndex: number,
202+
score: number
203+
): void {
204+
this.instances.get(id)?.updateScore(roundIndex, gameIndex, teamIndex, score);
205+
}
206+
207+
/** Advance round */
208+
advanceRound(id: string, fromRound?: number): TournamentData | undefined {
209+
return this.instances.get(id)?.advanceRound(fromRound);
210+
}
211+
212+
/** Destroy an instance */
213+
destroy(id: string): void {
214+
const instance = this.instances.get(id);
215+
if (instance) {
216+
instance.destroy();
217+
this.instances.delete(id);
218+
}
219+
}
220+
221+
/** Destroy all instances */
222+
destroyAll(): void {
223+
this.instances.forEach((instance) => instance.destroy());
224+
this.instances.clear();
225+
}
226+
}
227+
228+
export default GracketComponent;

src/adapters/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Framework adapters for Gracket
3+
*
4+
* Modern, framework-specific wrappers for the Gracket tournament bracket library.
5+
* Each adapter follows the latest best practices for its respective framework.
6+
*
7+
* @packageDocumentation
8+
*/
9+
10+
// Re-export core library
11+
export { Gracket } from '../core/Gracket';
12+
export * from '../types';
13+
14+
// Note: Framework adapters should be imported directly from their submodules:
15+
// - import { GracketReact } from 'gracket/react'
16+
// - import { GracketVue } from 'gracket/vue'
17+
// - import { GracketComponent } from 'gracket/angular'
18+
// - import { GracketSolid } from 'gracket/solid'
19+
// - import { gracket, createGracket } from 'gracket/svelte'
20+
// - import { GracketElement } from 'gracket/webcomponent'

0 commit comments

Comments
 (0)