@fuzo/soccer-board 0.2.0-alpha.1 → 0.2.0-alpha.2
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 +12 -22
- package/fesm2022/fuzo-soccer-board.mjs +323 -103
- package/fesm2022/fuzo-soccer-board.mjs.map +1 -1
- package/package.json +1 -1
- package/types/fuzo-soccer-board.d.ts +28 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
1
2
|
import * as i0 from '@angular/core';
|
|
2
3
|
import { inject, ElementRef, input, output, signal, computed, effect, HostListener, Component, PLATFORM_ID, viewChild, HostBinding } from '@angular/core';
|
|
3
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
4
4
|
import html2canvas from 'html2canvas-pro';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -201,6 +201,10 @@ class SoccerBoardPlayerComponent {
|
|
|
201
201
|
// Inputs using signals
|
|
202
202
|
player = input.required(...(ngDevMode ? [{ debugName: "player" }] : []));
|
|
203
203
|
size = input(SoccerBoardPlayerSize.Medium, ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
204
|
+
// Position inputs - when provided, the component positions itself
|
|
205
|
+
// These are percentage values (0-100) relative to parent container
|
|
206
|
+
positionLeft = input(null, ...(ngDevMode ? [{ debugName: "positionLeft" }] : []));
|
|
207
|
+
positionTop = input(null, ...(ngDevMode ? [{ debugName: "positionTop" }] : []));
|
|
204
208
|
// Outputs
|
|
205
209
|
dragStart = output();
|
|
206
210
|
dragging = output();
|
|
@@ -211,21 +215,14 @@ class SoccerBoardPlayerComponent {
|
|
|
211
215
|
isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
|
|
212
216
|
dragOffsetX = signal(0, ...(ngDevMode ? [{ debugName: "dragOffsetX" }] : []));
|
|
213
217
|
dragOffsetY = signal(0, ...(ngDevMode ? [{ debugName: "dragOffsetY" }] : []));
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
return {
|
|
224
|
-
opacity: '1',
|
|
225
|
-
cursor: 'grab',
|
|
226
|
-
zIndex: '',
|
|
227
|
-
};
|
|
228
|
-
}, ...(ngDevMode ? [{ debugName: "dragStyles" }] : []));
|
|
218
|
+
// Track if we're waiting for position update after drag
|
|
219
|
+
waitingForPositionUpdate = signal(false, ...(ngDevMode ? [{ debugName: "waitingForPositionUpdate" }] : []));
|
|
220
|
+
lastDragEndPosition = signal(null, ...(ngDevMode ? [{ debugName: "lastDragEndPosition" }] : []));
|
|
221
|
+
// Track if drag has actually started (mouse moved after mousedown)
|
|
222
|
+
isPotentialDrag = signal(false, ...(ngDevMode ? [{ debugName: "isPotentialDrag" }] : []));
|
|
223
|
+
dragStartX = signal(0, ...(ngDevMode ? [{ debugName: "dragStartX" }] : []));
|
|
224
|
+
dragStartY = signal(0, ...(ngDevMode ? [{ debugName: "dragStartY" }] : []));
|
|
225
|
+
DRAG_THRESHOLD = 5;
|
|
229
226
|
// Computed values
|
|
230
227
|
playerPhoto = computed(() => {
|
|
231
228
|
const photo = this.player()?.photo;
|
|
@@ -238,6 +235,10 @@ class SoccerBoardPlayerComponent {
|
|
|
238
235
|
const player = this.player();
|
|
239
236
|
return player?.fieldX !== undefined && player?.fieldY !== undefined;
|
|
240
237
|
}, ...(ngDevMode ? [{ debugName: "isOnField" }] : []));
|
|
238
|
+
// Determine if this component should position itself (has position inputs)
|
|
239
|
+
hasPosition = computed(() => {
|
|
240
|
+
return this.positionLeft() !== null && this.positionTop() !== null;
|
|
241
|
+
}, ...(ngDevMode ? [{ debugName: "hasPosition" }] : []));
|
|
241
242
|
// Show fieldPosition if on field, otherwise show preferredPosition
|
|
242
243
|
playerPosition = computed(() => {
|
|
243
244
|
const player = this.player();
|
|
@@ -259,74 +260,151 @@ class SoccerBoardPlayerComponent {
|
|
|
259
260
|
}
|
|
260
261
|
}, ...(ngDevMode ? [{ debugName: "sizeClasses" }] : []));
|
|
261
262
|
constructor() {
|
|
262
|
-
// Effect to apply
|
|
263
|
+
// Effect to apply position styles when not dragging
|
|
264
|
+
effect(() => {
|
|
265
|
+
const element = this.hostElement.nativeElement;
|
|
266
|
+
const dragging = this.isDragging();
|
|
267
|
+
const waiting = this.waitingForPositionUpdate();
|
|
268
|
+
const left = this.positionLeft();
|
|
269
|
+
const top = this.positionTop();
|
|
270
|
+
const hasPos = this.hasPosition();
|
|
271
|
+
const lastPos = this.lastDragEndPosition();
|
|
272
|
+
// Don't update position styles while dragging
|
|
273
|
+
if (dragging) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// If we're waiting for position update after drag ended
|
|
277
|
+
if (waiting && lastPos !== null) {
|
|
278
|
+
// Check if position has changed from what it was at drag end
|
|
279
|
+
// If position changed, we received the update and can apply the new position
|
|
280
|
+
if (left !== lastPos.left || top !== lastPos.top) {
|
|
281
|
+
this.waitingForPositionUpdate.set(false);
|
|
282
|
+
this.lastDragEndPosition.set(null);
|
|
283
|
+
// Continue to apply the new position below
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// Still waiting for position update, keep the element in its current visual position
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (hasPos && left !== null && top !== null) {
|
|
291
|
+
// Player is on the field - apply absolute positioning
|
|
292
|
+
element.style.position = 'absolute';
|
|
293
|
+
element.style.left = `${left}%`;
|
|
294
|
+
element.style.top = `${top}%`;
|
|
295
|
+
element.style.transform = 'translate(-50%, -50%)';
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Player is in sidebar - clear positioning styles
|
|
299
|
+
element.style.position = '';
|
|
300
|
+
element.style.left = '';
|
|
301
|
+
element.style.top = '';
|
|
302
|
+
element.style.transform = '';
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
// Effect to apply drag visual styles (opacity, cursor, zIndex)
|
|
263
306
|
effect(() => {
|
|
264
|
-
const
|
|
307
|
+
const dragging = this.isDragging();
|
|
265
308
|
const element = this.hostElement.nativeElement;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
309
|
+
if (dragging) {
|
|
310
|
+
element.style.opacity = '0.8';
|
|
311
|
+
element.style.cursor = 'grabbing';
|
|
312
|
+
element.style.zIndex = '1000';
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
element.style.opacity = '';
|
|
316
|
+
element.style.cursor = '';
|
|
317
|
+
element.style.zIndex = '';
|
|
318
|
+
}
|
|
269
319
|
});
|
|
270
320
|
}
|
|
271
321
|
onMouseDown(event) {
|
|
322
|
+
const target = event.target;
|
|
323
|
+
if (target.closest('.remove-badge')) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
272
326
|
event.preventDefault();
|
|
273
|
-
this.
|
|
327
|
+
this.preparePotentialDrag(event.clientX, event.clientY);
|
|
274
328
|
}
|
|
275
329
|
onTouchStart(event) {
|
|
276
330
|
if (event.touches.length !== 1)
|
|
277
331
|
return;
|
|
332
|
+
const target = event.target;
|
|
333
|
+
if (target.closest('.remove-badge')) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
278
336
|
event.preventDefault();
|
|
279
337
|
const touch = event.touches[0];
|
|
280
|
-
this.
|
|
338
|
+
this.preparePotentialDrag(touch.clientX, touch.clientY);
|
|
339
|
+
}
|
|
340
|
+
preparePotentialDrag(clientX, clientY) {
|
|
341
|
+
this.dragStartX.set(clientX);
|
|
342
|
+
this.dragStartY.set(clientY);
|
|
343
|
+
this.isPotentialDrag.set(true);
|
|
344
|
+
const element = this.hostElement.nativeElement;
|
|
345
|
+
const rect = element.getBoundingClientRect();
|
|
346
|
+
this.dragOffsetX.set(clientX - rect.left);
|
|
347
|
+
this.dragOffsetY.set(clientY - rect.top);
|
|
281
348
|
}
|
|
282
349
|
startDrag(clientX, clientY) {
|
|
283
|
-
console.log('[Player] Start drag');
|
|
284
350
|
const element = this.hostElement.nativeElement;
|
|
285
351
|
const rect = element.getBoundingClientRect();
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
// Apply fixed position first to maintain visual position
|
|
352
|
+
// IMPORTANT: Set dragging state FIRST
|
|
353
|
+
this.isDragging.set(true);
|
|
354
|
+
// Apply fixed position for smooth dragging
|
|
355
|
+
// Use exact current visual position to prevent any jump
|
|
291
356
|
element.style.position = 'fixed';
|
|
292
357
|
element.style.left = `${rect.left}px`;
|
|
293
358
|
element.style.top = `${rect.top}px`;
|
|
294
359
|
element.style.width = `${rect.width}px`;
|
|
295
360
|
element.style.height = `${rect.height}px`;
|
|
361
|
+
element.style.transform = 'none'; // Remove translate during drag
|
|
296
362
|
element.style.margin = '0';
|
|
297
|
-
|
|
298
|
-
this.dragOffsetX.set(offsetX);
|
|
299
|
-
this.dragOffsetY.set(offsetY);
|
|
300
|
-
this.isDragging.set(true);
|
|
363
|
+
element.style.pointerEvents = 'none';
|
|
301
364
|
const player = this.player();
|
|
302
|
-
console.log('[Player] Emitting dragStart', { playerId: player.id, playerName: player.name });
|
|
303
365
|
this.dragStart.emit({
|
|
304
366
|
playerId: player.id,
|
|
305
367
|
playerName: player.name,
|
|
306
368
|
});
|
|
307
369
|
}
|
|
308
370
|
onMouseMove(event) {
|
|
371
|
+
if (this.isPotentialDrag() && !this.isDragging()) {
|
|
372
|
+
const deltaX = Math.abs(event.clientX - this.dragStartX());
|
|
373
|
+
const deltaY = Math.abs(event.clientY - this.dragStartY());
|
|
374
|
+
if (deltaX > this.DRAG_THRESHOLD || deltaY > this.DRAG_THRESHOLD) {
|
|
375
|
+
this.startDrag(event.clientX, event.clientY);
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
309
379
|
if (!this.isDragging())
|
|
310
380
|
return;
|
|
311
381
|
event.preventDefault();
|
|
312
382
|
this.handleDrag(event.clientX, event.clientY);
|
|
313
383
|
}
|
|
314
384
|
onTouchMove(event) {
|
|
315
|
-
if (
|
|
385
|
+
if (event.touches.length !== 1)
|
|
316
386
|
return;
|
|
317
|
-
event.preventDefault();
|
|
318
387
|
const touch = event.touches[0];
|
|
388
|
+
if (this.isPotentialDrag() && !this.isDragging()) {
|
|
389
|
+
const deltaX = Math.abs(touch.clientX - this.dragStartX());
|
|
390
|
+
const deltaY = Math.abs(touch.clientY - this.dragStartY());
|
|
391
|
+
if (deltaX > this.DRAG_THRESHOLD || deltaY > this.DRAG_THRESHOLD) {
|
|
392
|
+
this.startDrag(touch.clientX, touch.clientY);
|
|
393
|
+
}
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (!this.isDragging())
|
|
397
|
+
return;
|
|
398
|
+
event.preventDefault();
|
|
319
399
|
this.handleDrag(touch.clientX, touch.clientY);
|
|
320
400
|
}
|
|
321
401
|
handleDrag(clientX, clientY) {
|
|
322
402
|
const element = this.hostElement.nativeElement;
|
|
323
403
|
const offsetX = this.dragOffsetX();
|
|
324
404
|
const offsetY = this.dragOffsetY();
|
|
325
|
-
//
|
|
326
|
-
// Position is already fixed from startDrag, just update coordinates
|
|
405
|
+
// Update position during drag
|
|
327
406
|
element.style.left = `${clientX - offsetX}px`;
|
|
328
407
|
element.style.top = `${clientY - offsetY}px`;
|
|
329
|
-
element.style.pointerEvents = 'none';
|
|
330
408
|
const player = this.player();
|
|
331
409
|
this.dragging.emit({
|
|
332
410
|
playerId: player.id,
|
|
@@ -337,63 +415,100 @@ class SoccerBoardPlayerComponent {
|
|
|
337
415
|
});
|
|
338
416
|
}
|
|
339
417
|
onMouseUp(event) {
|
|
418
|
+
if (this.isPotentialDrag() && !this.isDragging()) {
|
|
419
|
+
this.isPotentialDrag.set(false);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
340
422
|
if (!this.isDragging())
|
|
341
423
|
return;
|
|
342
424
|
this.endDrag(event.clientX, event.clientY);
|
|
343
425
|
}
|
|
344
426
|
onTouchEnd(event) {
|
|
427
|
+
if (this.isPotentialDrag() && !this.isDragging()) {
|
|
428
|
+
this.isPotentialDrag.set(false);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
345
431
|
if (!this.isDragging())
|
|
346
432
|
return;
|
|
347
433
|
const touch = event.changedTouches[0];
|
|
348
434
|
this.endDrag(touch.clientX, touch.clientY);
|
|
349
435
|
}
|
|
350
436
|
endDrag(clientX, clientY) {
|
|
351
|
-
console.log('[Player] End drag', { clientX, clientY });
|
|
352
|
-
this.isDragging.set(false);
|
|
353
437
|
const element = this.hostElement.nativeElement;
|
|
354
|
-
// Restore original styles
|
|
355
|
-
element.style.pointerEvents = '';
|
|
356
|
-
element.style.position = '';
|
|
357
|
-
element.style.left = '';
|
|
358
|
-
element.style.top = '';
|
|
359
|
-
element.style.width = '';
|
|
360
|
-
element.style.height = '';
|
|
361
|
-
element.style.margin = '';
|
|
362
438
|
const player = this.player();
|
|
439
|
+
const rect = element.getBoundingClientRect();
|
|
440
|
+
// Store current position inputs to detect when they change
|
|
441
|
+
const currentLeft = this.positionLeft();
|
|
442
|
+
const currentTop = this.positionTop();
|
|
443
|
+
// Emit event with all necessary information
|
|
363
444
|
const dragEndEvent = {
|
|
364
445
|
playerId: player.id,
|
|
365
446
|
clientX,
|
|
366
447
|
clientY,
|
|
367
448
|
offsetX: this.dragOffsetX(),
|
|
368
449
|
offsetY: this.dragOffsetY(),
|
|
450
|
+
elementWidth: rect.width,
|
|
451
|
+
elementHeight: rect.height,
|
|
369
452
|
};
|
|
370
|
-
console.log('[Player] Emitting dragEnd', dragEndEvent);
|
|
371
453
|
this.dragEnd.emit(dragEndEvent);
|
|
454
|
+
// Mark that we're waiting for position update
|
|
455
|
+
// Store the current position so we can detect when it changes
|
|
456
|
+
if (currentLeft !== null && currentTop !== null) {
|
|
457
|
+
this.waitingForPositionUpdate.set(true);
|
|
458
|
+
this.lastDragEndPosition.set({ left: currentLeft, top: currentTop });
|
|
459
|
+
}
|
|
460
|
+
// Clear drag state
|
|
461
|
+
this.isDragging.set(false);
|
|
462
|
+
this.isPotentialDrag.set(false);
|
|
463
|
+
// Clear drag-specific styles except position-related ones
|
|
464
|
+
// Position will be handled by the effect when new position arrives
|
|
465
|
+
element.style.width = '';
|
|
466
|
+
element.style.height = '';
|
|
467
|
+
element.style.margin = '';
|
|
468
|
+
element.style.pointerEvents = '';
|
|
469
|
+
// If we're waiting for position update, keep fixed position temporarily
|
|
470
|
+
// Otherwise, clear it and let effect handle positioning
|
|
471
|
+
if (!this.waitingForPositionUpdate()) {
|
|
472
|
+
element.style.position = '';
|
|
473
|
+
element.style.left = '';
|
|
474
|
+
element.style.top = '';
|
|
475
|
+
element.style.transform = '';
|
|
476
|
+
}
|
|
477
|
+
// Fallback: if position doesn't update within 500ms, clear waiting state
|
|
478
|
+
// This handles edge cases where the drop was outside the field
|
|
479
|
+
setTimeout(() => {
|
|
480
|
+
if (this.waitingForPositionUpdate()) {
|
|
481
|
+
this.waitingForPositionUpdate.set(false);
|
|
482
|
+
this.lastDragEndPosition.set(null);
|
|
483
|
+
// Clear fixed position and revert to input-based position
|
|
484
|
+
element.style.position = '';
|
|
485
|
+
element.style.left = '';
|
|
486
|
+
element.style.top = '';
|
|
487
|
+
element.style.transform = '';
|
|
488
|
+
}
|
|
489
|
+
}, 500);
|
|
372
490
|
}
|
|
373
491
|
onRemoveClick(event) {
|
|
374
492
|
event.stopPropagation();
|
|
375
493
|
event.preventDefault();
|
|
376
494
|
const player = this.player();
|
|
377
|
-
console.log('[Player] Remove from field clicked', player.id);
|
|
378
495
|
this.removeFromField.emit({ playerId: player.id });
|
|
379
496
|
}
|
|
380
497
|
onPlayerClick(event) {
|
|
381
|
-
|
|
382
|
-
if (this.isDragging()) {
|
|
498
|
+
if (this.isDragging() || this.isPotentialDrag()) {
|
|
383
499
|
return;
|
|
384
500
|
}
|
|
385
501
|
event.stopPropagation();
|
|
386
502
|
const player = this.player();
|
|
387
|
-
console.log('[Player] Player clicked', player.id);
|
|
388
503
|
this.playerClicked.emit({ playerId: player.id, player });
|
|
389
504
|
}
|
|
390
505
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SoccerBoardPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
391
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SoccerBoardPlayerComponent, isStandalone: true, selector: "lib-soccer-board-player", inputs: { player: { classPropertyName: "player", publicName: "player", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dragStart: "dragStart", dragging: "dragging", dragEnd: "dragEnd", removeFromField: "removeFromField", playerClicked: "playerClicked" }, host: { listeners: { "mousedown": "onMouseDown($event)", "touchstart": "onTouchStart($event)", "document:mousemove": "onMouseMove($event)", "document:touchmove": "onTouchMove($event)", "document:mouseup": "onMouseUp($event)", "document:touchend": "onTouchEnd($event)" } }, ngImport: i0, template: "<div\n class=\"player-card group select-none cursor-pointer\"\n [class]=\"sizeClasses()\"\n (click)=\"onPlayerClick($event)\"\n>\n <!-- Golden Border Wrapper -->\n <div class=\"player-border-wrapper\">\n <!-- Main Content Container -->\n <div class=\"player-content relative w-full h-full overflow-hidden rounded-md shadow-lg\">\n <!-- Top Left: Number and Position - Above the photo -->\n <div class=\"player-info-top-left absolute left-0.5 top-1 flex flex-col gap-0.5 z-30\">\n <!-- Player Number -->\n @if (playerNumber()) {\n <div class=\"player-number-badge text-white text-xs font-bold\">\n {{ playerNumber() }}\n </div>\n }\n\n <!-- Position -->\n @if (playerPosition()) {\n <div class=\"player-position-badge text-white text-[10px] font-bold\">\n {{ playerPosition() }}\n </div>\n }\n </div>\n\n <!-- Right Side: Player Photo -->\n <div class=\"player-photo-container absolute right-0 bottom-0 w-4/5 overflow-visible\">\n @if (playerPhoto()) {\n <img\n [src]=\"playerPhoto()!\"\n [alt]=\"playerName()\"\n class=\"player-photo-image\"\n />\n } @else {\n <!-- Placeholder -->\n <div class=\"w-full h-full flex items-center justify-center bg-gradient-to-br from-gray-400 to-gray-600\">\n <svg class=\"w-8 h-8 text-white opacity-50\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fill-rule=\"evenodd\" d=\"M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </div>\n\n <!-- Hover Effect Overlay -->\n <div class=\"absolute inset-0 bg-white/0 group-hover:bg-white/10 transition-all duration-200 rounded-md pointer-events-none\"></div>\n </div>\n </div>\n\n <!-- Remove Button (only when on field) - Outside the card -->\n @if (isOnField()) {\n <button\n class=\"remove-badge absolute
|
|
506
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SoccerBoardPlayerComponent, isStandalone: true, selector: "lib-soccer-board-player", inputs: { player: { classPropertyName: "player", publicName: "player", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, positionLeft: { classPropertyName: "positionLeft", publicName: "positionLeft", isSignal: true, isRequired: false, transformFunction: null }, positionTop: { classPropertyName: "positionTop", publicName: "positionTop", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dragStart: "dragStart", dragging: "dragging", dragEnd: "dragEnd", removeFromField: "removeFromField", playerClicked: "playerClicked" }, host: { listeners: { "mousedown": "onMouseDown($event)", "touchstart": "onTouchStart($event)", "document:mousemove": "onMouseMove($event)", "document:touchmove": "onTouchMove($event)", "document:mouseup": "onMouseUp($event)", "document:touchend": "onTouchEnd($event)" } }, ngImport: i0, template: "<div\n class=\"player-card group select-none cursor-pointer\"\n [class]=\"sizeClasses()\"\n (click)=\"onPlayerClick($event)\"\n>\n <!-- Golden Border Wrapper -->\n <div class=\"player-border-wrapper\">\n <!-- Main Content Container -->\n <div class=\"player-content relative w-full h-full overflow-hidden rounded-md shadow-lg\">\n <!-- Top Left: Number and Position - Above the photo -->\n <div class=\"player-info-top-left absolute left-0.5 top-1 flex flex-col gap-0.5 z-30\">\n <!-- Player Number -->\n @if (playerNumber()) {\n <div class=\"player-number-badge text-white text-xs font-bold\">\n {{ playerNumber() }}\n </div>\n }\n\n <!-- Position -->\n @if (playerPosition()) {\n <div class=\"player-position-badge text-white text-[10px] font-bold\">\n {{ playerPosition() }}\n </div>\n }\n </div>\n\n <!-- Right Side: Player Photo -->\n <div class=\"player-photo-container absolute right-0 bottom-0 w-4/5 overflow-visible\">\n @if (playerPhoto()) {\n <img\n [src]=\"playerPhoto()!\"\n [alt]=\"playerName()\"\n class=\"player-photo-image\"\n />\n } @else {\n <!-- Placeholder -->\n <div class=\"w-full h-full flex items-center justify-center bg-gradient-to-br from-gray-400 to-gray-600\">\n <svg class=\"w-8 h-8 text-white opacity-50\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fill-rule=\"evenodd\" d=\"M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </div>\n\n <!-- Hover Effect Overlay -->\n <div class=\"absolute inset-0 bg-white/0 group-hover:bg-white/10 transition-all duration-200 rounded-md pointer-events-none\"></div>\n </div>\n </div>\n\n <!-- Remove Button (only when on field) - Outside the card -->\n @if (isOnField()) {\n <button\n class=\"remove-badge absolute text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center shadow-lg transition-colors z-30 border-2 border-white\"\n (click)=\"onRemoveClick($event)\"\n (mousedown)=\"$event.stopPropagation()\"\n (touchstart)=\"$event.stopPropagation()\"\n type=\"button\"\n aria-label=\"Remove from field\"\n >\n \u00D7\n </button>\n }\n\n <!-- Player Name (outside the card, below) -->\n <div class=\"player-name-outside text-white text-[10px] font-semibold truncate text-center leading-tight mt-0.5\">\n {{ playerName() }}\n </div>\n</div>\n", styles: [":host{display:inline-block;position:relative;cursor:grab;user-select:none;-webkit-user-select:none;touch-action:none}.player-card{position:relative;transition:transform .2s ease,box-shadow .2s ease}.player-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px #0000004d}.player-card:active{transform:scale(.95)}.player-border-wrapper{position:relative;width:100%;height:100%;padding:2px;border-radius:8px;background:linear-gradient(135deg,#d4af37,#f4d03f,#d4af37,#f4d03f,#d4af37);background-size:200% 200%;animation:shimmer 3s ease-in-out infinite;box-shadow:0 0 8px #d4af3780}.player-content{position:relative;width:100%;height:100%;border-radius:6px;background:repeating-linear-gradient(45deg,#16a34a 0px 2px,#22c55e 2px 4px,#16a34a 4px 6px,#15803d 6px 8px),linear-gradient(135deg,#15803d,#16a34a,#22c55e,#16a34a,#15803d);background-size:20px 20px,100% 100%;background-blend-mode:overlay}@keyframes shimmer{0%,to{background-position:0% 50%}50%{background-position:100% 50%}}.player-info-top-left{z-index:30;text-shadow:1px 1px 2px rgba(0,0,0,.8),0 0 4px rgba(0,0,0,.5);background:linear-gradient(to right,rgba(0,0,0,.3) 0%,transparent 50%);padding-right:.5rem;border-radius:0 .25rem .25rem 0}.player-number-badge{font-size:12px;font-weight:900;line-height:1;letter-spacing:-.5px}.player-position-badge{font-size:9px;font-weight:700;line-height:1;letter-spacing:.5px}.remove-badge{background-color:#ef4444!important;color:#fff!important;border-color:#fff!important;box-shadow:0 2px 8px #0006;cursor:pointer!important;top:-6px;right:-6px;pointer-events:auto!important;user-select:none;-webkit-user-select:none}.remove-badge:hover{background-color:#dc2626!important}.remove-badge:active{background-color:#b91c1c!important;transform:scale(.95)}.remove-badge:before,.remove-badge:after{pointer-events:none}.player-photo-container{height:100%;width:85%;display:flex;align-items:flex-end;justify-content:flex-start;z-index:1;overflow:visible}.player-photo-image{width:100%;height:auto;max-height:140%;object-fit:contain;object-position:center bottom;display:block}.player-name-outside{width:100%;text-shadow:1px 1px 2px rgba(0,0,0,.8),0 0 4px rgba(0,0,0,.5)}:host-context(.w-12) .player-info-top-left{left:.125rem;top:.25rem;gap:.125rem}:host-context(.w-12) .player-number-badge{font-size:9px}:host-context(.w-12) .player-position-badge{font-size:7px}:host-context(.w-12) .player-name-outside{font-size:8px}:host-context(.w-12) .remove-badge{width:18px;height:18px;font-size:10px;top:-2px;right:-2px}:host-context(.w-20) .player-info-top-left{left:.375rem;top:.5rem;gap:.25rem}:host-context(.w-20) .player-number-badge{font-size:16px}:host-context(.w-20) .player-position-badge{font-size:11px}:host-context(.w-20) .player-name-outside{font-size:12px}:host-context(.w-20) .remove-badge{width:24px;height:24px;font-size:14px;top:-3px;right:-3px}\n"] });
|
|
392
507
|
}
|
|
393
508
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SoccerBoardPlayerComponent, decorators: [{
|
|
394
509
|
type: Component,
|
|
395
|
-
args: [{ selector: 'lib-soccer-board-player', standalone: true, imports: [], template: "<div\n class=\"player-card group select-none cursor-pointer\"\n [class]=\"sizeClasses()\"\n (click)=\"onPlayerClick($event)\"\n>\n <!-- Golden Border Wrapper -->\n <div class=\"player-border-wrapper\">\n <!-- Main Content Container -->\n <div class=\"player-content relative w-full h-full overflow-hidden rounded-md shadow-lg\">\n <!-- Top Left: Number and Position - Above the photo -->\n <div class=\"player-info-top-left absolute left-0.5 top-1 flex flex-col gap-0.5 z-30\">\n <!-- Player Number -->\n @if (playerNumber()) {\n <div class=\"player-number-badge text-white text-xs font-bold\">\n {{ playerNumber() }}\n </div>\n }\n\n <!-- Position -->\n @if (playerPosition()) {\n <div class=\"player-position-badge text-white text-[10px] font-bold\">\n {{ playerPosition() }}\n </div>\n }\n </div>\n\n <!-- Right Side: Player Photo -->\n <div class=\"player-photo-container absolute right-0 bottom-0 w-4/5 overflow-visible\">\n @if (playerPhoto()) {\n <img\n [src]=\"playerPhoto()!\"\n [alt]=\"playerName()\"\n class=\"player-photo-image\"\n />\n } @else {\n <!-- Placeholder -->\n <div class=\"w-full h-full flex items-center justify-center bg-gradient-to-br from-gray-400 to-gray-600\">\n <svg class=\"w-8 h-8 text-white opacity-50\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fill-rule=\"evenodd\" d=\"M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </div>\n\n <!-- Hover Effect Overlay -->\n <div class=\"absolute inset-0 bg-white/0 group-hover:bg-white/10 transition-all duration-200 rounded-md pointer-events-none\"></div>\n </div>\n </div>\n\n <!-- Remove Button (only when on field) - Outside the card -->\n @if (isOnField()) {\n <button\n class=\"remove-badge absolute
|
|
396
|
-
}], ctorParameters: () => [], propDecorators: { player: [{ type: i0.Input, args: [{ isSignal: true, alias: "player", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], dragStart: [{ type: i0.Output, args: ["dragStart"] }], dragging: [{ type: i0.Output, args: ["dragging"] }], dragEnd: [{ type: i0.Output, args: ["dragEnd"] }], removeFromField: [{ type: i0.Output, args: ["removeFromField"] }], playerClicked: [{ type: i0.Output, args: ["playerClicked"] }], onMouseDown: [{
|
|
510
|
+
args: [{ selector: 'lib-soccer-board-player', standalone: true, imports: [], template: "<div\n class=\"player-card group select-none cursor-pointer\"\n [class]=\"sizeClasses()\"\n (click)=\"onPlayerClick($event)\"\n>\n <!-- Golden Border Wrapper -->\n <div class=\"player-border-wrapper\">\n <!-- Main Content Container -->\n <div class=\"player-content relative w-full h-full overflow-hidden rounded-md shadow-lg\">\n <!-- Top Left: Number and Position - Above the photo -->\n <div class=\"player-info-top-left absolute left-0.5 top-1 flex flex-col gap-0.5 z-30\">\n <!-- Player Number -->\n @if (playerNumber()) {\n <div class=\"player-number-badge text-white text-xs font-bold\">\n {{ playerNumber() }}\n </div>\n }\n\n <!-- Position -->\n @if (playerPosition()) {\n <div class=\"player-position-badge text-white text-[10px] font-bold\">\n {{ playerPosition() }}\n </div>\n }\n </div>\n\n <!-- Right Side: Player Photo -->\n <div class=\"player-photo-container absolute right-0 bottom-0 w-4/5 overflow-visible\">\n @if (playerPhoto()) {\n <img\n [src]=\"playerPhoto()!\"\n [alt]=\"playerName()\"\n class=\"player-photo-image\"\n />\n } @else {\n <!-- Placeholder -->\n <div class=\"w-full h-full flex items-center justify-center bg-gradient-to-br from-gray-400 to-gray-600\">\n <svg class=\"w-8 h-8 text-white opacity-50\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fill-rule=\"evenodd\" d=\"M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </div>\n\n <!-- Hover Effect Overlay -->\n <div class=\"absolute inset-0 bg-white/0 group-hover:bg-white/10 transition-all duration-200 rounded-md pointer-events-none\"></div>\n </div>\n </div>\n\n <!-- Remove Button (only when on field) - Outside the card -->\n @if (isOnField()) {\n <button\n class=\"remove-badge absolute text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center shadow-lg transition-colors z-30 border-2 border-white\"\n (click)=\"onRemoveClick($event)\"\n (mousedown)=\"$event.stopPropagation()\"\n (touchstart)=\"$event.stopPropagation()\"\n type=\"button\"\n aria-label=\"Remove from field\"\n >\n \u00D7\n </button>\n }\n\n <!-- Player Name (outside the card, below) -->\n <div class=\"player-name-outside text-white text-[10px] font-semibold truncate text-center leading-tight mt-0.5\">\n {{ playerName() }}\n </div>\n</div>\n", styles: [":host{display:inline-block;position:relative;cursor:grab;user-select:none;-webkit-user-select:none;touch-action:none}.player-card{position:relative;transition:transform .2s ease,box-shadow .2s ease}.player-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px #0000004d}.player-card:active{transform:scale(.95)}.player-border-wrapper{position:relative;width:100%;height:100%;padding:2px;border-radius:8px;background:linear-gradient(135deg,#d4af37,#f4d03f,#d4af37,#f4d03f,#d4af37);background-size:200% 200%;animation:shimmer 3s ease-in-out infinite;box-shadow:0 0 8px #d4af3780}.player-content{position:relative;width:100%;height:100%;border-radius:6px;background:repeating-linear-gradient(45deg,#16a34a 0px 2px,#22c55e 2px 4px,#16a34a 4px 6px,#15803d 6px 8px),linear-gradient(135deg,#15803d,#16a34a,#22c55e,#16a34a,#15803d);background-size:20px 20px,100% 100%;background-blend-mode:overlay}@keyframes shimmer{0%,to{background-position:0% 50%}50%{background-position:100% 50%}}.player-info-top-left{z-index:30;text-shadow:1px 1px 2px rgba(0,0,0,.8),0 0 4px rgba(0,0,0,.5);background:linear-gradient(to right,rgba(0,0,0,.3) 0%,transparent 50%);padding-right:.5rem;border-radius:0 .25rem .25rem 0}.player-number-badge{font-size:12px;font-weight:900;line-height:1;letter-spacing:-.5px}.player-position-badge{font-size:9px;font-weight:700;line-height:1;letter-spacing:.5px}.remove-badge{background-color:#ef4444!important;color:#fff!important;border-color:#fff!important;box-shadow:0 2px 8px #0006;cursor:pointer!important;top:-6px;right:-6px;pointer-events:auto!important;user-select:none;-webkit-user-select:none}.remove-badge:hover{background-color:#dc2626!important}.remove-badge:active{background-color:#b91c1c!important;transform:scale(.95)}.remove-badge:before,.remove-badge:after{pointer-events:none}.player-photo-container{height:100%;width:85%;display:flex;align-items:flex-end;justify-content:flex-start;z-index:1;overflow:visible}.player-photo-image{width:100%;height:auto;max-height:140%;object-fit:contain;object-position:center bottom;display:block}.player-name-outside{width:100%;text-shadow:1px 1px 2px rgba(0,0,0,.8),0 0 4px rgba(0,0,0,.5)}:host-context(.w-12) .player-info-top-left{left:.125rem;top:.25rem;gap:.125rem}:host-context(.w-12) .player-number-badge{font-size:9px}:host-context(.w-12) .player-position-badge{font-size:7px}:host-context(.w-12) .player-name-outside{font-size:8px}:host-context(.w-12) .remove-badge{width:18px;height:18px;font-size:10px;top:-2px;right:-2px}:host-context(.w-20) .player-info-top-left{left:.375rem;top:.5rem;gap:.25rem}:host-context(.w-20) .player-number-badge{font-size:16px}:host-context(.w-20) .player-position-badge{font-size:11px}:host-context(.w-20) .player-name-outside{font-size:12px}:host-context(.w-20) .remove-badge{width:24px;height:24px;font-size:14px;top:-3px;right:-3px}\n"] }]
|
|
511
|
+
}], ctorParameters: () => [], propDecorators: { player: [{ type: i0.Input, args: [{ isSignal: true, alias: "player", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], positionLeft: [{ type: i0.Input, args: [{ isSignal: true, alias: "positionLeft", required: false }] }], positionTop: [{ type: i0.Input, args: [{ isSignal: true, alias: "positionTop", required: false }] }], dragStart: [{ type: i0.Output, args: ["dragStart"] }], dragging: [{ type: i0.Output, args: ["dragging"] }], dragEnd: [{ type: i0.Output, args: ["dragEnd"] }], removeFromField: [{ type: i0.Output, args: ["removeFromField"] }], playerClicked: [{ type: i0.Output, args: ["playerClicked"] }], onMouseDown: [{
|
|
397
512
|
type: HostListener,
|
|
398
513
|
args: ['mousedown', ['$event']]
|
|
399
514
|
}], onTouchStart: [{
|
|
@@ -446,6 +561,12 @@ class SoccerBoardComponent {
|
|
|
446
561
|
playerRemovedFromField = output();
|
|
447
562
|
imageExported = output();
|
|
448
563
|
playerClicked = output();
|
|
564
|
+
// Method to handle player drag start (called from template)
|
|
565
|
+
onPlayerDragStart(event) {
|
|
566
|
+
// Player component handles its own drag positioning
|
|
567
|
+
// This is just for logging/debugging
|
|
568
|
+
console.log('[SoccerBoard] Drag started', event.playerId);
|
|
569
|
+
}
|
|
449
570
|
// Method to handle player drag end (called from template)
|
|
450
571
|
onPlayerDragEnd(event) {
|
|
451
572
|
this.handlePlayerDrop(event);
|
|
@@ -519,11 +640,22 @@ class SoccerBoardComponent {
|
|
|
519
640
|
});
|
|
520
641
|
}, ...(ngDevMode ? [{ debugName: "awayZones" }] : []));
|
|
521
642
|
// Computed players by side
|
|
643
|
+
// These computed signals automatically cache results when dependencies haven't changed
|
|
644
|
+
// The @for loop uses trackBy with player.id to only re-render changed players
|
|
645
|
+
// NOTE: Even if a new array is returned, trackBy ensures only changed items re-render
|
|
522
646
|
homePlayers = computed(() => {
|
|
523
|
-
return this.players().filter((player) => player.side === SoccerBoardTeamSide.Home &&
|
|
647
|
+
return this.players().filter((player) => player.side === SoccerBoardTeamSide.Home &&
|
|
648
|
+
player.fieldX !== undefined &&
|
|
649
|
+
player.fieldY !== undefined &&
|
|
650
|
+
!isNaN(player.fieldX) &&
|
|
651
|
+
!isNaN(player.fieldY));
|
|
524
652
|
}, ...(ngDevMode ? [{ debugName: "homePlayers" }] : []));
|
|
525
653
|
awayPlayers = computed(() => {
|
|
526
|
-
return this.players().filter((player) => player.side === SoccerBoardTeamSide.Away &&
|
|
654
|
+
return this.players().filter((player) => player.side === SoccerBoardTeamSide.Away &&
|
|
655
|
+
player.fieldX !== undefined &&
|
|
656
|
+
player.fieldY !== undefined &&
|
|
657
|
+
!isNaN(player.fieldX) &&
|
|
658
|
+
!isNaN(player.fieldY));
|
|
527
659
|
}, ...(ngDevMode ? [{ debugName: "awayPlayers" }] : []));
|
|
528
660
|
// Computed: players available (not on field)
|
|
529
661
|
availablePlayers = computed(() => {
|
|
@@ -550,12 +682,51 @@ class SoccerBoardComponent {
|
|
|
550
682
|
playersGridColumns = computed(() => {
|
|
551
683
|
return `repeat(${this.playersColumns()}, minmax(0, 1fr))`;
|
|
552
684
|
}, ...(ngDevMode ? [{ debugName: "playersGridColumns" }] : []));
|
|
685
|
+
// Computed: Map of player positions for stable rendering
|
|
686
|
+
// This ensures positions are only recalculated when players() or orientation() change
|
|
687
|
+
// Using a Map allows efficient lookups
|
|
688
|
+
playerPositions = computed(() => {
|
|
689
|
+
const positions = new Map();
|
|
690
|
+
const players = this.players();
|
|
691
|
+
// Track orientation dependency - fieldToHalfCoordinates uses it internally
|
|
692
|
+
this.orientation();
|
|
693
|
+
// Process all players with valid coordinates
|
|
694
|
+
for (const player of players) {
|
|
695
|
+
if (player.fieldX !== undefined &&
|
|
696
|
+
player.fieldY !== undefined &&
|
|
697
|
+
player.side !== undefined &&
|
|
698
|
+
!isNaN(player.fieldX) &&
|
|
699
|
+
!isNaN(player.fieldY)) {
|
|
700
|
+
// Validate coordinates are within bounds (0-100)
|
|
701
|
+
const fieldX = Math.max(0, Math.min(100, player.fieldX));
|
|
702
|
+
const fieldY = Math.max(0, Math.min(100, player.fieldY));
|
|
703
|
+
// Calculate position using validated coordinates
|
|
704
|
+
const position = this.fieldToHalfCoordinates(fieldX, fieldY, player.side);
|
|
705
|
+
positions.set(player.id, position);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return positions;
|
|
709
|
+
}, ...(ngDevMode ? [{ debugName: "playerPositions" }] : []));
|
|
553
710
|
// Helper method to get visual position for a player (public for template)
|
|
711
|
+
// Now uses the computed positions map instead of calculating on the fly
|
|
554
712
|
getPlayerPosition(player) {
|
|
555
|
-
|
|
556
|
-
|
|
713
|
+
const positions = this.playerPositions();
|
|
714
|
+
const position = positions.get(player.id);
|
|
715
|
+
if (position) {
|
|
716
|
+
return position;
|
|
717
|
+
}
|
|
718
|
+
// Fallback: if position is not in cache but player has coordinates, calculate it
|
|
719
|
+
// This should rarely happen, but handles edge cases
|
|
720
|
+
if (player.fieldX !== undefined &&
|
|
721
|
+
player.fieldY !== undefined &&
|
|
722
|
+
player.side !== undefined &&
|
|
723
|
+
!isNaN(player.fieldX) &&
|
|
724
|
+
!isNaN(player.fieldY)) {
|
|
725
|
+
const fieldX = Math.max(0, Math.min(100, player.fieldX));
|
|
726
|
+
const fieldY = Math.max(0, Math.min(100, player.fieldY));
|
|
727
|
+
return this.fieldToHalfCoordinates(fieldX, fieldY, player.side);
|
|
557
728
|
}
|
|
558
|
-
return
|
|
729
|
+
return { left: 0, top: 0 };
|
|
559
730
|
}
|
|
560
731
|
resizeObserver = null;
|
|
561
732
|
constructor() {
|
|
@@ -588,57 +759,106 @@ class SoccerBoardComponent {
|
|
|
588
759
|
*/
|
|
589
760
|
handlePlayerDrop(event) {
|
|
590
761
|
console.log('[SoccerBoard] handlePlayerDrop called', event);
|
|
591
|
-
|
|
762
|
+
// Calculate position immediately (don't use requestAnimationFrame as it causes timing issues)
|
|
763
|
+
// The coordinates are already correct from the drag end event
|
|
764
|
+
// clientX/clientY represent where the mouse/touch was when released
|
|
765
|
+
// offsetX/offsetY represent the offset from element's top-left to click point
|
|
766
|
+
// elementWidth/elementHeight are the dimensions of the dragged element
|
|
767
|
+
const position = this.calculateFieldPosition(event.clientX, event.clientY, event.offsetX, event.offsetY, event.elementWidth, event.elementHeight);
|
|
592
768
|
console.log('[SoccerBoard] Calculated position', position);
|
|
593
|
-
if (position) {
|
|
594
|
-
// Check if player is already on the field (moving to a different position)
|
|
595
|
-
const currentPlayer = this.players().find((p) => p.id === event.playerId);
|
|
596
|
-
const isMovingPlayer = currentPlayer?.fieldX !== undefined && currentPlayer?.fieldY !== undefined;
|
|
597
|
-
// Check maximum players per side (only for new players, not when moving)
|
|
598
|
-
if (!isMovingPlayer) {
|
|
599
|
-
const playersOnSide = this.players().filter((p) => p.side === position.side && p.fieldX !== undefined && p.fieldY !== undefined).length;
|
|
600
|
-
if (playersOnSide >= this.maxPlayersPerSide()) {
|
|
601
|
-
console.warn(`[SoccerBoard] Maximum players (${this.maxPlayersPerSide()}) reached for ${position.side} side`);
|
|
602
|
-
return; // Don't allow drop if max players reached
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
const tacticalPosition = this.detectTacticalPosition(position.fieldX, position.fieldY, position.side);
|
|
606
|
-
console.log('🎯 Player dropped:', {
|
|
607
|
-
playerId: event.playerId,
|
|
608
|
-
position: tacticalPosition,
|
|
609
|
-
coordinates: {
|
|
610
|
-
fieldX: position.fieldX,
|
|
611
|
-
fieldY: position.fieldY,
|
|
612
|
-
side: position.side,
|
|
613
|
-
},
|
|
614
|
-
});
|
|
615
|
-
// Emit event with position information
|
|
616
|
-
this.playerPositioned.emit({
|
|
617
|
-
playerId: event.playerId,
|
|
618
|
-
fieldX: position.fieldX,
|
|
619
|
-
fieldY: position.fieldY,
|
|
620
|
-
side: position.side,
|
|
621
|
-
position: tacticalPosition,
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
else {
|
|
769
|
+
if (!position) {
|
|
625
770
|
console.warn('[SoccerBoard] Position is null - player not dropped on field');
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
// Get current player state BEFORE validation to avoid race conditions
|
|
774
|
+
// Use the current players() signal value to get the latest state
|
|
775
|
+
const currentPlayers = this.players();
|
|
776
|
+
const currentPlayer = currentPlayers.find((p) => p.id === event.playerId);
|
|
777
|
+
if (!currentPlayer) {
|
|
778
|
+
console.warn('[SoccerBoard] Player not found', event.playerId);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const isMovingPlayer = currentPlayer.fieldX !== undefined && currentPlayer.fieldY !== undefined;
|
|
782
|
+
const isMovingToDifferentSide = isMovingPlayer && currentPlayer.side !== position.side;
|
|
783
|
+
// Check maximum players per side:
|
|
784
|
+
// - For new players: always check
|
|
785
|
+
// - For moving players: only check if moving to a DIFFERENT side
|
|
786
|
+
// - When moving within the same side, allow it (no limit check needed)
|
|
787
|
+
if (!isMovingPlayer || isMovingToDifferentSide) {
|
|
788
|
+
// Count players on the target side (excluding the current player if moving)
|
|
789
|
+
const playersOnTargetSide = currentPlayers.filter((p) => p.id !== event.playerId && // Exclude the player being moved
|
|
790
|
+
p.side === position.side &&
|
|
791
|
+
p.fieldX !== undefined &&
|
|
792
|
+
p.fieldY !== undefined &&
|
|
793
|
+
!isNaN(p.fieldX) &&
|
|
794
|
+
!isNaN(p.fieldY)).length;
|
|
795
|
+
if (playersOnTargetSide >= this.maxPlayersPerSide()) {
|
|
796
|
+
console.warn(`[SoccerBoard] Maximum players (${this.maxPlayersPerSide()}) reached for ${position.side} side. Cannot ${isMovingPlayer ? 'move' : 'add'} player.`);
|
|
797
|
+
return; // Don't allow drop if max players reached on target side
|
|
798
|
+
}
|
|
626
799
|
}
|
|
800
|
+
// Validate coordinates are within bounds
|
|
801
|
+
const fieldX = Math.max(0, Math.min(100, position.fieldX));
|
|
802
|
+
const fieldY = Math.max(0, Math.min(100, position.fieldY));
|
|
803
|
+
const tacticalPosition = this.detectTacticalPosition(fieldX, fieldY, position.side);
|
|
804
|
+
console.log('🎯 Player dropped:', {
|
|
805
|
+
playerId: event.playerId,
|
|
806
|
+
isMovingPlayer,
|
|
807
|
+
isMovingToDifferentSide,
|
|
808
|
+
position: tacticalPosition,
|
|
809
|
+
coordinates: {
|
|
810
|
+
fieldX,
|
|
811
|
+
fieldY,
|
|
812
|
+
side: position.side,
|
|
813
|
+
},
|
|
814
|
+
});
|
|
815
|
+
// Emit event with position information (using validated coordinates)
|
|
816
|
+
// Emit immediately - the parent component handles the update
|
|
817
|
+
this.playerPositioned.emit({
|
|
818
|
+
playerId: event.playerId,
|
|
819
|
+
fieldX,
|
|
820
|
+
fieldY,
|
|
821
|
+
side: position.side,
|
|
822
|
+
position: tacticalPosition,
|
|
823
|
+
});
|
|
627
824
|
}
|
|
628
825
|
/**
|
|
629
826
|
* Calculates field position from client coordinates
|
|
827
|
+
* @param clientX - X coordinate of the mouse/touch event
|
|
828
|
+
* @param clientY - Y coordinate of the mouse/touch event
|
|
829
|
+
* @param offsetX - Optional: X offset from element's left edge to click point (used to calculate center)
|
|
830
|
+
* @param offsetY - Optional: Y offset from element's top edge to click point (used to calculate center)
|
|
831
|
+
* @param elementWidth - Optional: Width of the dragged element
|
|
832
|
+
* @param elementHeight - Optional: Height of the dragged element
|
|
630
833
|
*/
|
|
631
|
-
calculateFieldPosition(clientX, clientY) {
|
|
834
|
+
calculateFieldPosition(clientX, clientY, offsetX, offsetY, elementWidth, elementHeight) {
|
|
632
835
|
const homeHalfEl = this.homeHalf()?.nativeElement;
|
|
633
836
|
const awayHalfEl = this.awayHalf()?.nativeElement;
|
|
634
837
|
if (!homeHalfEl || !awayHalfEl) {
|
|
635
838
|
return null;
|
|
636
839
|
}
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
|
|
641
|
-
|
|
840
|
+
// Calculate the center of the dragged element
|
|
841
|
+
// clientX/clientY is where the cursor is
|
|
842
|
+
// offsetX/offsetY is the offset from the element's top-left to where the user clicked
|
|
843
|
+
// elementWidth/elementHeight are the actual dimensions of the element
|
|
844
|
+
//
|
|
845
|
+
// The element's top-left corner is at:
|
|
846
|
+
// elementLeft = clientX - offsetX
|
|
847
|
+
// elementTop = clientY - offsetY
|
|
848
|
+
//
|
|
849
|
+
// The element's center is at:
|
|
850
|
+
// centerX = elementLeft + elementWidth / 2 = clientX - offsetX + elementWidth / 2
|
|
851
|
+
// centerY = elementTop + elementHeight / 2 = clientY - offsetY + elementHeight / 2
|
|
852
|
+
let centerX = clientX;
|
|
853
|
+
let centerY = clientY;
|
|
854
|
+
if (offsetX !== undefined &&
|
|
855
|
+
offsetY !== undefined &&
|
|
856
|
+
elementWidth !== undefined &&
|
|
857
|
+
elementHeight !== undefined) {
|
|
858
|
+
// Calculate the actual center of the element
|
|
859
|
+
centerX = clientX - offsetX + elementWidth / 2;
|
|
860
|
+
centerY = clientY - offsetY + elementHeight / 2;
|
|
861
|
+
}
|
|
642
862
|
// Check HOME half
|
|
643
863
|
const homeRect = homeHalfEl.getBoundingClientRect();
|
|
644
864
|
const isInHome = centerX >= homeRect.left &&
|
|
@@ -953,11 +1173,11 @@ class SoccerBoardComponent {
|
|
|
953
1173
|
}
|
|
954
1174
|
}
|
|
955
1175
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SoccerBoardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
956
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SoccerBoardComponent, isStandalone: true, selector: "fuzo-soccer-board", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, teamSide: { classPropertyName: "teamSide", publicName: "teamSide", isSignal: true, isRequired: false, transformFunction: null }, showPositions: { classPropertyName: "showPositions", publicName: "showPositions", isSignal: true, isRequired: false, transformFunction: null }, fit: { classPropertyName: "fit", publicName: "fit", isSignal: true, isRequired: false, transformFunction: null }, players: { classPropertyName: "players", publicName: "players", isSignal: true, isRequired: false, transformFunction: null }, showPlayersSidebar: { classPropertyName: "showPlayersSidebar", publicName: "showPlayersSidebar", isSignal: true, isRequired: false, transformFunction: null }, playersPosition: { classPropertyName: "playersPosition", publicName: "playersPosition", isSignal: true, isRequired: false, transformFunction: null }, playersColumns: { classPropertyName: "playersColumns", publicName: "playersColumns", isSignal: true, isRequired: false, transformFunction: null }, maxPlayersPerSide: { classPropertyName: "maxPlayersPerSide", publicName: "maxPlayersPerSide", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playerPositioned: "playerPositioned", playerRemovedFromField: "playerRemovedFromField", imageExported: "imageExported", playerClicked: "playerClicked" }, host: { properties: { "style.width": "this.hostWidthStyle" } }, viewQueries: [{ propertyName: "fieldContainer", first: true, predicate: ["fieldContainer"], descendants: true, isSignal: true }, { propertyName: "homeHalf", first: true, predicate: ["homeHalf"], descendants: true, isSignal: true }, { propertyName: "awayHalf", first: true, predicate: ["awayHalf"], descendants: true, isSignal: true }, { propertyName: "soccerBoardContainer", first: true, predicate: ["soccerBoardContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"soccer-board-wrapper\" [class]=\"layoutClasses()\">\n <!-- Players Sidebar -->\n @if (showPlayersSidebar()) {\n <div class=\"players-sidebar\">\n <div\n class=\"players-grid\"\n [style.grid-template-columns]=\"playersGridColumns()\"\n >\n @for (player of availablePlayers(); track player.id) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Medium\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n </div>\n </div>\n }\n\n <!-- Soccer Board Container -->\n <div #soccerBoardContainer class=\"soccer-board-container\">\n <div\n #fieldContainer\n class=\"field-container\"\n [class.landscape]=\"isLandscape()\"\n >\n <!-- AWAY half -->\n <div #awayHalf class=\"field-half away\" [class.hidden]=\"!showAway()\">\n <div class=\"field-lines\">\n <div class=\"center-border\"></div>\n <div class=\"half-circle\"></div>\n <div class=\"penalty-area\"></div>\n <div class=\"goal-area\"></div>\n </div>\n\n <!-- Position zones -->\n @if (showPositions()) {\n <div class=\"position-zones\">\n @for (\n zone of awayZones();\n track zone.id + zone.coords.x + zone.coords.y\n ) {\n <div\n class=\"position-zone\"\n [style.left.%]=\"zone.coords.x\"\n [style.top.%]=\"zone.coords.y\"\n [style.width.%]=\"zone.coords.width\"\n [style.height.%]=\"zone.coords.height\"\n [style.background]=\"zone.color\"\n >\n {{ zone.label }}\n </div>\n }\n </div>\n }\n\n <!-- Players container -->\n <div class=\"players-container\" id=\"players-away\">\n @for (player of awayPlayers(); track player.id) {\n @if (player.fieldX !== undefined && player.fieldY !== undefined) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Small\"\n [style.position]=\"'absolute'\"\n [style.left.%]=\"getPlayerPosition(player).left\"\n [style.top.%]=\"getPlayerPosition(player).top\"\n [style.transform]=\"'translate(-50%, -50%)'\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (removeFromField)=\"onPlayerRemoveFromField($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n }\n </div>\n </div>\n\n <!-- HOME half -->\n <div #homeHalf class=\"field-half home\" [class.hidden]=\"!showHome()\">\n <div class=\"field-lines\">\n <div class=\"center-border\"></div>\n <div class=\"half-circle\"></div>\n <div class=\"penalty-area\"></div>\n <div class=\"goal-area\"></div>\n </div>\n\n <!-- Position zones -->\n @if (showPositions()) {\n <div class=\"position-zones\">\n @for (\n zone of homeZones();\n track zone.id + zone.coords.x + zone.coords.y\n ) {\n <div\n class=\"position-zone\"\n [style.left.%]=\"zone.coords.x\"\n [style.top.%]=\"zone.coords.y\"\n [style.width.%]=\"zone.coords.width\"\n [style.height.%]=\"zone.coords.height\"\n [style.background]=\"zone.color\"\n >\n {{ zone.label }}\n </div>\n }\n </div>\n }\n\n <!-- Players container -->\n <div class=\"players-container\" id=\"players-home\">\n @for (player of homePlayers(); track player.id) {\n @if (player.fieldX !== undefined && player.fieldY !== undefined) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Small\"\n [style.position]=\"'absolute'\"\n [style.left.%]=\"getPlayerPosition(player).left\"\n [style.top.%]=\"getPlayerPosition(player).top\"\n [style.transform]=\"'translate(-50%, -50%)'\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (removeFromField)=\"onPlayerRemoveFromField($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n }\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;width:100%;height:100%}*{box-sizing:border-box}.soccer-board-wrapper{display:flex;width:100%;height:100%;gap:1.5rem}.layout-players-left{flex-direction:row}.layout-players-left .players-sidebar{width:300px;min-width:300px;flex-shrink:0}.layout-players-left .soccer-board-container{flex:1;min-width:0}.layout-players-right{flex-direction:row-reverse}.layout-players-right .players-sidebar{width:300px;min-width:300px;flex-shrink:0}.layout-players-right .soccer-board-container{flex:1;min-width:0}.layout-players-top{flex-direction:column}.layout-players-top .players-sidebar{width:100%;max-height:300px}.layout-players-top .soccer-board-container{flex:1;min-height:0}.layout-players-bottom{flex-direction:column-reverse}.layout-players-bottom .players-sidebar{width:100%;max-height:300px}.layout-players-bottom .soccer-board-container{flex:1;min-height:0}.layout-no-sidebar .soccer-board-container{width:100%;height:100%}.players-sidebar{display:flex;flex-direction:column;background:transparent;padding:0;overflow-y:auto}.players-grid{display:grid;gap:.5rem;align-content:start}.soccer-board-container{width:100%;display:flex;justify-content:center;align-items:center;padding:var(--soccer-board-padding, 0);max-width:var(--soccer-board-max-width, none);width:var(--soccer-board-width, 100%);aspect-ratio:105/68}.field-container{display:flex;flex-direction:column;gap:0;width:100%;min-width:300px;height:auto}.field-container.landscape{flex-direction:row}.field-container.landscape .field-half{width:50%;aspect-ratio:52.5/68;background:repeating-linear-gradient(90deg,#2d5016 0px 10px,#3a6b1f 10px 20px)}.field-container.landscape .field-half.home{border-radius:0 10px 10px 0;box-shadow:10px 0 30px #0000004d}.field-container.landscape .field-half.away{border-radius:10px 0 0 10px;box-shadow:-5px 0 20px #0003}.field-container.landscape .field-half.home .center-border{inset:0 auto 0 0;width:3px;height:auto}.field-container.landscape .field-half.away .center-border{inset:0 0 0 auto;width:3px;height:auto}.field-container.landscape .field-half.home .half-circle{top:50%;left:0;width:17.4%;height:26.9%;transform:translateY(-50%);border:2px solid rgba(255,255,255,.5);border-left:none;border-top:2px solid rgba(255,255,255,.5);border-bottom:2px solid rgba(255,255,255,.5);border-radius:0 999px 999px 0}.field-container.landscape .field-half.away .half-circle{inset:50% 0 auto auto;width:17.4%;height:26.9%;transform:translateY(-50%);border:2px solid rgba(255,255,255,.5);border-right:none;border-top:2px solid rgba(255,255,255,.5);border-bottom:2px solid rgba(255,255,255,.5);border-radius:999px 0 0 999px}.field-container.landscape .field-half.home .penalty-area{inset:50% 0 auto auto;transform:translateY(-50%);height:59.3%;width:31.4%;border:2px solid rgba(255,255,255,.5);border-right:none}.field-container.landscape .field-half.away .penalty-area{inset:50% auto auto 0;transform:translateY(-50%);height:59.3%;width:31.4%;border:2px solid rgba(255,255,255,.5);border-left:none}.field-container.landscape .field-half.home .goal-area{inset:50% 0 auto auto;transform:translateY(-50%);height:26.9%;width:10.5%;border:2px solid rgba(255,255,255,.5);border-right:none}.field-container.landscape .field-half.away .goal-area{inset:50% auto auto 0;transform:translateY(-50%);height:26.9%;width:10.5%;border:2px solid rgba(255,255,255,.5);border-left:none}.field-half{position:relative;width:100%;aspect-ratio:68/52.5;background:repeating-linear-gradient(0deg,#2d5016 0px 10px,#3a6b1f 10px 20px);overflow:hidden}.field-half.home{border-radius:0 0 10px 10px;box-shadow:0 10px 30px #0000004d}.field-half.away{border-radius:10px 10px 0 0;box-shadow:0 -5px 20px #0003}.field-half.hidden{display:none}.field-lines{position:absolute;inset:0;pointer-events:none}.field-half.home .center-border{position:absolute;background:#fffc;z-index:10;top:0;left:0;right:0;height:3px}.field-half.away .center-border{position:absolute;background:#fffc;z-index:10;bottom:0;left:0;right:0;height:3px}.field-half.home .half-circle{position:absolute;z-index:5;top:0;left:50%;width:26.9%;height:17.4%;transform:translate(-50%);border:2px solid rgba(255,255,255,.5);border-top:none;border-radius:0 0 999px 999px;background:transparent}.field-half.away .half-circle{position:absolute;z-index:5;bottom:0;left:50%;width:26.9%;height:17.4%;transform:translate(-50%);border:2px solid rgba(255,255,255,.5);border-bottom:none;border-radius:999px 999px 0 0;background:transparent}.field-half.home .penalty-area{position:absolute;z-index:5;bottom:0;left:50%;transform:translate(-50%);width:59.3%;height:31.4%;border:2px solid rgba(255,255,255,.5);border-bottom:none}.field-half.away .penalty-area{position:absolute;z-index:5;top:0;left:50%;transform:translate(-50%);width:59.3%;height:31.4%;border:2px solid rgba(255,255,255,.5);border-top:none}.field-half.home .goal-area{position:absolute;z-index:5;bottom:0;left:50%;transform:translate(-50%);width:26.9%;height:10.5%;border:2px solid rgba(255,255,255,.5);border-bottom:none}.field-half.away .goal-area{position:absolute;z-index:5;top:0;left:50%;transform:translate(-50%);width:26.9%;height:10.5%;border:2px solid rgba(255,255,255,.5);border-top:none}.position-zones{position:absolute;inset:0;pointer-events:none;z-index:15}.position-zones.hidden{display:none}.position-zone{position:absolute;border:2px solid rgba(255,255,255,.8);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;color:#fff;text-shadow:0 2px 4px rgba(0,0,0,.8);opacity:.85;transition:all .3s ease}.position-zone:hover{opacity:1;z-index:100;transform:scale(1.05)}.players-container{position:absolute;inset:0;pointer-events:none;z-index:20}.players-container>*{pointer-events:auto}\n"], dependencies: [{ kind: "component", type: SoccerBoardPlayerComponent, selector: "lib-soccer-board-player", inputs: ["player", "size"], outputs: ["dragStart", "dragging", "dragEnd", "removeFromField", "playerClicked"] }] });
|
|
1176
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SoccerBoardComponent, isStandalone: true, selector: "fuzo-soccer-board", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, teamSide: { classPropertyName: "teamSide", publicName: "teamSide", isSignal: true, isRequired: false, transformFunction: null }, showPositions: { classPropertyName: "showPositions", publicName: "showPositions", isSignal: true, isRequired: false, transformFunction: null }, fit: { classPropertyName: "fit", publicName: "fit", isSignal: true, isRequired: false, transformFunction: null }, players: { classPropertyName: "players", publicName: "players", isSignal: true, isRequired: false, transformFunction: null }, showPlayersSidebar: { classPropertyName: "showPlayersSidebar", publicName: "showPlayersSidebar", isSignal: true, isRequired: false, transformFunction: null }, playersPosition: { classPropertyName: "playersPosition", publicName: "playersPosition", isSignal: true, isRequired: false, transformFunction: null }, playersColumns: { classPropertyName: "playersColumns", publicName: "playersColumns", isSignal: true, isRequired: false, transformFunction: null }, maxPlayersPerSide: { classPropertyName: "maxPlayersPerSide", publicName: "maxPlayersPerSide", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playerPositioned: "playerPositioned", playerRemovedFromField: "playerRemovedFromField", imageExported: "imageExported", playerClicked: "playerClicked" }, host: { properties: { "style.width": "this.hostWidthStyle" } }, viewQueries: [{ propertyName: "fieldContainer", first: true, predicate: ["fieldContainer"], descendants: true, isSignal: true }, { propertyName: "homeHalf", first: true, predicate: ["homeHalf"], descendants: true, isSignal: true }, { propertyName: "awayHalf", first: true, predicate: ["awayHalf"], descendants: true, isSignal: true }, { propertyName: "soccerBoardContainer", first: true, predicate: ["soccerBoardContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"soccer-board-wrapper\" [class]=\"layoutClasses()\">\n <!-- Players Sidebar -->\n @if (showPlayersSidebar()) {\n <div class=\"players-sidebar\">\n <div\n class=\"players-grid\"\n [style.grid-template-columns]=\"playersGridColumns()\"\n >\n @for (player of availablePlayers(); track player.id) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Medium\"\n (dragStart)=\"onPlayerDragStart($event)\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n </div>\n </div>\n }\n\n <!-- Soccer Board Container -->\n <div #soccerBoardContainer class=\"soccer-board-container\">\n <div\n #fieldContainer\n class=\"field-container\"\n [class.landscape]=\"isLandscape()\"\n >\n <!-- AWAY half -->\n <div #awayHalf class=\"field-half away\" [class.hidden]=\"!showAway()\">\n <div class=\"field-lines\">\n <div class=\"center-border\"></div>\n <div class=\"half-circle\"></div>\n <div class=\"penalty-area\"></div>\n <div class=\"goal-area\"></div>\n </div>\n\n <!-- Position zones -->\n @if (showPositions()) {\n <div class=\"position-zones\">\n @for (\n zone of awayZones();\n track zone.id + zone.coords.x + zone.coords.y\n ) {\n <div\n class=\"position-zone\"\n [style.left.%]=\"zone.coords.x\"\n [style.top.%]=\"zone.coords.y\"\n [style.width.%]=\"zone.coords.width\"\n [style.height.%]=\"zone.coords.height\"\n [style.background]=\"zone.color\"\n >\n {{ zone.label }}\n </div>\n }\n </div>\n }\n\n <!-- Players container -->\n <div class=\"players-container\" id=\"players-away\">\n @for (player of awayPlayers(); track player.id) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Small\"\n [positionLeft]=\"getPlayerPosition(player).left\"\n [positionTop]=\"getPlayerPosition(player).top\"\n (dragStart)=\"onPlayerDragStart($event)\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (removeFromField)=\"onPlayerRemoveFromField($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n </div>\n </div>\n\n <!-- HOME half -->\n <div #homeHalf class=\"field-half home\" [class.hidden]=\"!showHome()\">\n <div class=\"field-lines\">\n <div class=\"center-border\"></div>\n <div class=\"half-circle\"></div>\n <div class=\"penalty-area\"></div>\n <div class=\"goal-area\"></div>\n </div>\n\n <!-- Position zones -->\n @if (showPositions()) {\n <div class=\"position-zones\">\n @for (\n zone of homeZones();\n track zone.id + zone.coords.x + zone.coords.y\n ) {\n <div\n class=\"position-zone\"\n [style.left.%]=\"zone.coords.x\"\n [style.top.%]=\"zone.coords.y\"\n [style.width.%]=\"zone.coords.width\"\n [style.height.%]=\"zone.coords.height\"\n [style.background]=\"zone.color\"\n >\n {{ zone.label }}\n </div>\n }\n </div>\n }\n\n <!-- Players container -->\n <div class=\"players-container\" id=\"players-home\">\n @for (player of homePlayers(); track player.id) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Small\"\n [positionLeft]=\"getPlayerPosition(player).left\"\n [positionTop]=\"getPlayerPosition(player).top\"\n (dragStart)=\"onPlayerDragStart($event)\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (removeFromField)=\"onPlayerRemoveFromField($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;width:100%;height:100%}*{box-sizing:border-box}.soccer-board-wrapper{display:flex;width:100%;height:100%;gap:1.5rem}.layout-players-left{flex-direction:row}.layout-players-left .players-sidebar{width:300px;min-width:300px;flex-shrink:0}.layout-players-left .soccer-board-container{flex:1;min-width:0}.layout-players-right{flex-direction:row-reverse}.layout-players-right .players-sidebar{width:300px;min-width:300px;flex-shrink:0}.layout-players-right .soccer-board-container{flex:1;min-width:0}.layout-players-top{flex-direction:column}.layout-players-top .players-sidebar{width:100%;max-height:300px}.layout-players-top .soccer-board-container{flex:1;min-height:0}.layout-players-bottom{flex-direction:column-reverse}.layout-players-bottom .players-sidebar{width:100%;max-height:300px}.layout-players-bottom .soccer-board-container{flex:1;min-height:0}.layout-no-sidebar .soccer-board-container{width:100%;height:100%}.players-sidebar{display:flex;flex-direction:column;background:transparent;padding:0;overflow-y:auto}.players-grid{display:grid;gap:.5rem;align-content:start}.soccer-board-container{width:100%;display:flex;justify-content:center;align-items:center;padding:var(--soccer-board-padding, 0);max-width:var(--soccer-board-max-width, none);width:var(--soccer-board-width, 100%);aspect-ratio:105/68}.field-container{display:flex;flex-direction:column;gap:0;width:100%;min-width:300px;height:auto}.field-container.landscape{flex-direction:row}.field-container.landscape .field-half{width:50%;aspect-ratio:52.5/68;background:repeating-linear-gradient(90deg,#2d5016 0px 10px,#3a6b1f 10px 20px)}.field-container.landscape .field-half.home{border-radius:0 10px 10px 0;box-shadow:10px 0 30px #0000004d}.field-container.landscape .field-half.away{border-radius:10px 0 0 10px;box-shadow:-5px 0 20px #0003}.field-container.landscape .field-half.home .center-border{inset:0 auto 0 0;width:3px;height:auto}.field-container.landscape .field-half.away .center-border{inset:0 0 0 auto;width:3px;height:auto}.field-container.landscape .field-half.home .half-circle{top:50%;left:0;width:17.4%;height:26.9%;transform:translateY(-50%);border:2px solid rgba(255,255,255,.5);border-left:none;border-top:2px solid rgba(255,255,255,.5);border-bottom:2px solid rgba(255,255,255,.5);border-radius:0 999px 999px 0}.field-container.landscape .field-half.away .half-circle{inset:50% 0 auto auto;width:17.4%;height:26.9%;transform:translateY(-50%);border:2px solid rgba(255,255,255,.5);border-right:none;border-top:2px solid rgba(255,255,255,.5);border-bottom:2px solid rgba(255,255,255,.5);border-radius:999px 0 0 999px}.field-container.landscape .field-half.home .penalty-area{inset:50% 0 auto auto;transform:translateY(-50%);height:59.3%;width:31.4%;border:2px solid rgba(255,255,255,.5);border-right:none}.field-container.landscape .field-half.away .penalty-area{inset:50% auto auto 0;transform:translateY(-50%);height:59.3%;width:31.4%;border:2px solid rgba(255,255,255,.5);border-left:none}.field-container.landscape .field-half.home .goal-area{inset:50% 0 auto auto;transform:translateY(-50%);height:26.9%;width:10.5%;border:2px solid rgba(255,255,255,.5);border-right:none}.field-container.landscape .field-half.away .goal-area{inset:50% auto auto 0;transform:translateY(-50%);height:26.9%;width:10.5%;border:2px solid rgba(255,255,255,.5);border-left:none}.field-half{position:relative;width:100%;aspect-ratio:68/52.5;background:repeating-linear-gradient(0deg,#2d5016 0px 10px,#3a6b1f 10px 20px);overflow:hidden}.field-half.home{border-radius:0 0 10px 10px;box-shadow:0 10px 30px #0000004d}.field-half.away{border-radius:10px 10px 0 0;box-shadow:0 -5px 20px #0003}.field-half.hidden{display:none}.field-lines{position:absolute;inset:0;pointer-events:none}.field-half.home .center-border{position:absolute;background:#fffc;z-index:10;top:0;left:0;right:0;height:3px}.field-half.away .center-border{position:absolute;background:#fffc;z-index:10;bottom:0;left:0;right:0;height:3px}.field-half.home .half-circle{position:absolute;z-index:5;top:0;left:50%;width:26.9%;height:17.4%;transform:translate(-50%);border:2px solid rgba(255,255,255,.5);border-top:none;border-radius:0 0 999px 999px;background:transparent}.field-half.away .half-circle{position:absolute;z-index:5;bottom:0;left:50%;width:26.9%;height:17.4%;transform:translate(-50%);border:2px solid rgba(255,255,255,.5);border-bottom:none;border-radius:999px 999px 0 0;background:transparent}.field-half.home .penalty-area{position:absolute;z-index:5;bottom:0;left:50%;transform:translate(-50%);width:59.3%;height:31.4%;border:2px solid rgba(255,255,255,.5);border-bottom:none}.field-half.away .penalty-area{position:absolute;z-index:5;top:0;left:50%;transform:translate(-50%);width:59.3%;height:31.4%;border:2px solid rgba(255,255,255,.5);border-top:none}.field-half.home .goal-area{position:absolute;z-index:5;bottom:0;left:50%;transform:translate(-50%);width:26.9%;height:10.5%;border:2px solid rgba(255,255,255,.5);border-bottom:none}.field-half.away .goal-area{position:absolute;z-index:5;top:0;left:50%;transform:translate(-50%);width:26.9%;height:10.5%;border:2px solid rgba(255,255,255,.5);border-top:none}.position-zones{position:absolute;inset:0;pointer-events:none;z-index:15}.position-zones.hidden{display:none}.position-zone{position:absolute;border:2px solid rgba(255,255,255,.8);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;color:#fff;text-shadow:0 2px 4px rgba(0,0,0,.8);opacity:.85;transition:all .3s ease}.position-zone:hover{opacity:1;z-index:100;transform:scale(1.05)}.players-container{position:absolute;inset:0;pointer-events:none;z-index:20}.players-container>*{pointer-events:auto}\n"], dependencies: [{ kind: "component", type: SoccerBoardPlayerComponent, selector: "lib-soccer-board-player", inputs: ["player", "size", "positionLeft", "positionTop"], outputs: ["dragStart", "dragging", "dragEnd", "removeFromField", "playerClicked"] }] });
|
|
957
1177
|
}
|
|
958
1178
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SoccerBoardComponent, decorators: [{
|
|
959
1179
|
type: Component,
|
|
960
|
-
args: [{ selector: 'fuzo-soccer-board', standalone: true, imports: [SoccerBoardPlayerComponent], template: "<div class=\"soccer-board-wrapper\" [class]=\"layoutClasses()\">\n <!-- Players Sidebar -->\n @if (showPlayersSidebar()) {\n <div class=\"players-sidebar\">\n <div\n class=\"players-grid\"\n [style.grid-template-columns]=\"playersGridColumns()\"\n >\n @for (player of availablePlayers(); track player.id) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Medium\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n </div>\n </div>\n }\n\n <!-- Soccer Board Container -->\n <div #soccerBoardContainer class=\"soccer-board-container\">\n <div\n #fieldContainer\n class=\"field-container\"\n [class.landscape]=\"isLandscape()\"\n >\n <!-- AWAY half -->\n <div #awayHalf class=\"field-half away\" [class.hidden]=\"!showAway()\">\n <div class=\"field-lines\">\n <div class=\"center-border\"></div>\n <div class=\"half-circle\"></div>\n <div class=\"penalty-area\"></div>\n <div class=\"goal-area\"></div>\n </div>\n\n <!-- Position zones -->\n @if (showPositions()) {\n <div class=\"position-zones\">\n @for (\n zone of awayZones();\n track zone.id + zone.coords.x + zone.coords.y\n ) {\n <div\n class=\"position-zone\"\n [style.left.%]=\"zone.coords.x\"\n [style.top.%]=\"zone.coords.y\"\n [style.width.%]=\"zone.coords.width\"\n [style.height.%]=\"zone.coords.height\"\n [style.background]=\"zone.color\"\n >\n {{ zone.label }}\n </div>\n }\n </div>\n }\n\n <!-- Players container -->\n <div class=\"players-container\" id=\"players-away\">\n @for (player of awayPlayers(); track player.id) {\n @if (player.fieldX !== undefined && player.fieldY !== undefined) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Small\"\n [style.position]=\"'absolute'\"\n [style.left.%]=\"getPlayerPosition(player).left\"\n [style.top.%]=\"getPlayerPosition(player).top\"\n [style.transform]=\"'translate(-50%, -50%)'\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (removeFromField)=\"onPlayerRemoveFromField($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n }\n </div>\n </div>\n\n <!-- HOME half -->\n <div #homeHalf class=\"field-half home\" [class.hidden]=\"!showHome()\">\n <div class=\"field-lines\">\n <div class=\"center-border\"></div>\n <div class=\"half-circle\"></div>\n <div class=\"penalty-area\"></div>\n <div class=\"goal-area\"></div>\n </div>\n\n <!-- Position zones -->\n @if (showPositions()) {\n <div class=\"position-zones\">\n @for (\n zone of homeZones();\n track zone.id + zone.coords.x + zone.coords.y\n ) {\n <div\n class=\"position-zone\"\n [style.left.%]=\"zone.coords.x\"\n [style.top.%]=\"zone.coords.y\"\n [style.width.%]=\"zone.coords.width\"\n [style.height.%]=\"zone.coords.height\"\n [style.background]=\"zone.color\"\n >\n {{ zone.label }}\n </div>\n }\n </div>\n }\n\n <!-- Players container -->\n <div class=\"players-container\" id=\"players-home\">\n @for (player of homePlayers(); track player.id) {\n @if (player.fieldX !== undefined && player.fieldY !== undefined) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Small\"\n [style.position]=\"'absolute'\"\n [style.left.%]=\"getPlayerPosition(player).left\"\n [style.top.%]=\"getPlayerPosition(player).top\"\n [style.transform]=\"'translate(-50%, -50%)'\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (removeFromField)=\"onPlayerRemoveFromField($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n }\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;width:100%;height:100%}*{box-sizing:border-box}.soccer-board-wrapper{display:flex;width:100%;height:100%;gap:1.5rem}.layout-players-left{flex-direction:row}.layout-players-left .players-sidebar{width:300px;min-width:300px;flex-shrink:0}.layout-players-left .soccer-board-container{flex:1;min-width:0}.layout-players-right{flex-direction:row-reverse}.layout-players-right .players-sidebar{width:300px;min-width:300px;flex-shrink:0}.layout-players-right .soccer-board-container{flex:1;min-width:0}.layout-players-top{flex-direction:column}.layout-players-top .players-sidebar{width:100%;max-height:300px}.layout-players-top .soccer-board-container{flex:1;min-height:0}.layout-players-bottom{flex-direction:column-reverse}.layout-players-bottom .players-sidebar{width:100%;max-height:300px}.layout-players-bottom .soccer-board-container{flex:1;min-height:0}.layout-no-sidebar .soccer-board-container{width:100%;height:100%}.players-sidebar{display:flex;flex-direction:column;background:transparent;padding:0;overflow-y:auto}.players-grid{display:grid;gap:.5rem;align-content:start}.soccer-board-container{width:100%;display:flex;justify-content:center;align-items:center;padding:var(--soccer-board-padding, 0);max-width:var(--soccer-board-max-width, none);width:var(--soccer-board-width, 100%);aspect-ratio:105/68}.field-container{display:flex;flex-direction:column;gap:0;width:100%;min-width:300px;height:auto}.field-container.landscape{flex-direction:row}.field-container.landscape .field-half{width:50%;aspect-ratio:52.5/68;background:repeating-linear-gradient(90deg,#2d5016 0px 10px,#3a6b1f 10px 20px)}.field-container.landscape .field-half.home{border-radius:0 10px 10px 0;box-shadow:10px 0 30px #0000004d}.field-container.landscape .field-half.away{border-radius:10px 0 0 10px;box-shadow:-5px 0 20px #0003}.field-container.landscape .field-half.home .center-border{inset:0 auto 0 0;width:3px;height:auto}.field-container.landscape .field-half.away .center-border{inset:0 0 0 auto;width:3px;height:auto}.field-container.landscape .field-half.home .half-circle{top:50%;left:0;width:17.4%;height:26.9%;transform:translateY(-50%);border:2px solid rgba(255,255,255,.5);border-left:none;border-top:2px solid rgba(255,255,255,.5);border-bottom:2px solid rgba(255,255,255,.5);border-radius:0 999px 999px 0}.field-container.landscape .field-half.away .half-circle{inset:50% 0 auto auto;width:17.4%;height:26.9%;transform:translateY(-50%);border:2px solid rgba(255,255,255,.5);border-right:none;border-top:2px solid rgba(255,255,255,.5);border-bottom:2px solid rgba(255,255,255,.5);border-radius:999px 0 0 999px}.field-container.landscape .field-half.home .penalty-area{inset:50% 0 auto auto;transform:translateY(-50%);height:59.3%;width:31.4%;border:2px solid rgba(255,255,255,.5);border-right:none}.field-container.landscape .field-half.away .penalty-area{inset:50% auto auto 0;transform:translateY(-50%);height:59.3%;width:31.4%;border:2px solid rgba(255,255,255,.5);border-left:none}.field-container.landscape .field-half.home .goal-area{inset:50% 0 auto auto;transform:translateY(-50%);height:26.9%;width:10.5%;border:2px solid rgba(255,255,255,.5);border-right:none}.field-container.landscape .field-half.away .goal-area{inset:50% auto auto 0;transform:translateY(-50%);height:26.9%;width:10.5%;border:2px solid rgba(255,255,255,.5);border-left:none}.field-half{position:relative;width:100%;aspect-ratio:68/52.5;background:repeating-linear-gradient(0deg,#2d5016 0px 10px,#3a6b1f 10px 20px);overflow:hidden}.field-half.home{border-radius:0 0 10px 10px;box-shadow:0 10px 30px #0000004d}.field-half.away{border-radius:10px 10px 0 0;box-shadow:0 -5px 20px #0003}.field-half.hidden{display:none}.field-lines{position:absolute;inset:0;pointer-events:none}.field-half.home .center-border{position:absolute;background:#fffc;z-index:10;top:0;left:0;right:0;height:3px}.field-half.away .center-border{position:absolute;background:#fffc;z-index:10;bottom:0;left:0;right:0;height:3px}.field-half.home .half-circle{position:absolute;z-index:5;top:0;left:50%;width:26.9%;height:17.4%;transform:translate(-50%);border:2px solid rgba(255,255,255,.5);border-top:none;border-radius:0 0 999px 999px;background:transparent}.field-half.away .half-circle{position:absolute;z-index:5;bottom:0;left:50%;width:26.9%;height:17.4%;transform:translate(-50%);border:2px solid rgba(255,255,255,.5);border-bottom:none;border-radius:999px 999px 0 0;background:transparent}.field-half.home .penalty-area{position:absolute;z-index:5;bottom:0;left:50%;transform:translate(-50%);width:59.3%;height:31.4%;border:2px solid rgba(255,255,255,.5);border-bottom:none}.field-half.away .penalty-area{position:absolute;z-index:5;top:0;left:50%;transform:translate(-50%);width:59.3%;height:31.4%;border:2px solid rgba(255,255,255,.5);border-top:none}.field-half.home .goal-area{position:absolute;z-index:5;bottom:0;left:50%;transform:translate(-50%);width:26.9%;height:10.5%;border:2px solid rgba(255,255,255,.5);border-bottom:none}.field-half.away .goal-area{position:absolute;z-index:5;top:0;left:50%;transform:translate(-50%);width:26.9%;height:10.5%;border:2px solid rgba(255,255,255,.5);border-top:none}.position-zones{position:absolute;inset:0;pointer-events:none;z-index:15}.position-zones.hidden{display:none}.position-zone{position:absolute;border:2px solid rgba(255,255,255,.8);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;color:#fff;text-shadow:0 2px 4px rgba(0,0,0,.8);opacity:.85;transition:all .3s ease}.position-zone:hover{opacity:1;z-index:100;transform:scale(1.05)}.players-container{position:absolute;inset:0;pointer-events:none;z-index:20}.players-container>*{pointer-events:auto}\n"] }]
|
|
1180
|
+
args: [{ selector: 'fuzo-soccer-board', standalone: true, imports: [SoccerBoardPlayerComponent], template: "<div class=\"soccer-board-wrapper\" [class]=\"layoutClasses()\">\n <!-- Players Sidebar -->\n @if (showPlayersSidebar()) {\n <div class=\"players-sidebar\">\n <div\n class=\"players-grid\"\n [style.grid-template-columns]=\"playersGridColumns()\"\n >\n @for (player of availablePlayers(); track player.id) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Medium\"\n (dragStart)=\"onPlayerDragStart($event)\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n </div>\n </div>\n }\n\n <!-- Soccer Board Container -->\n <div #soccerBoardContainer class=\"soccer-board-container\">\n <div\n #fieldContainer\n class=\"field-container\"\n [class.landscape]=\"isLandscape()\"\n >\n <!-- AWAY half -->\n <div #awayHalf class=\"field-half away\" [class.hidden]=\"!showAway()\">\n <div class=\"field-lines\">\n <div class=\"center-border\"></div>\n <div class=\"half-circle\"></div>\n <div class=\"penalty-area\"></div>\n <div class=\"goal-area\"></div>\n </div>\n\n <!-- Position zones -->\n @if (showPositions()) {\n <div class=\"position-zones\">\n @for (\n zone of awayZones();\n track zone.id + zone.coords.x + zone.coords.y\n ) {\n <div\n class=\"position-zone\"\n [style.left.%]=\"zone.coords.x\"\n [style.top.%]=\"zone.coords.y\"\n [style.width.%]=\"zone.coords.width\"\n [style.height.%]=\"zone.coords.height\"\n [style.background]=\"zone.color\"\n >\n {{ zone.label }}\n </div>\n }\n </div>\n }\n\n <!-- Players container -->\n <div class=\"players-container\" id=\"players-away\">\n @for (player of awayPlayers(); track player.id) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Small\"\n [positionLeft]=\"getPlayerPosition(player).left\"\n [positionTop]=\"getPlayerPosition(player).top\"\n (dragStart)=\"onPlayerDragStart($event)\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (removeFromField)=\"onPlayerRemoveFromField($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n </div>\n </div>\n\n <!-- HOME half -->\n <div #homeHalf class=\"field-half home\" [class.hidden]=\"!showHome()\">\n <div class=\"field-lines\">\n <div class=\"center-border\"></div>\n <div class=\"half-circle\"></div>\n <div class=\"penalty-area\"></div>\n <div class=\"goal-area\"></div>\n </div>\n\n <!-- Position zones -->\n @if (showPositions()) {\n <div class=\"position-zones\">\n @for (\n zone of homeZones();\n track zone.id + zone.coords.x + zone.coords.y\n ) {\n <div\n class=\"position-zone\"\n [style.left.%]=\"zone.coords.x\"\n [style.top.%]=\"zone.coords.y\"\n [style.width.%]=\"zone.coords.width\"\n [style.height.%]=\"zone.coords.height\"\n [style.background]=\"zone.color\"\n >\n {{ zone.label }}\n </div>\n }\n </div>\n }\n\n <!-- Players container -->\n <div class=\"players-container\" id=\"players-home\">\n @for (player of homePlayers(); track player.id) {\n <lib-soccer-board-player\n [player]=\"player\"\n [size]=\"PlayerSize.Small\"\n [positionLeft]=\"getPlayerPosition(player).left\"\n [positionTop]=\"getPlayerPosition(player).top\"\n (dragStart)=\"onPlayerDragStart($event)\"\n (dragEnd)=\"onPlayerDragEnd($event)\"\n (removeFromField)=\"onPlayerRemoveFromField($event)\"\n (playerClicked)=\"onPlayerClick($event)\"\n />\n }\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;width:100%;height:100%}*{box-sizing:border-box}.soccer-board-wrapper{display:flex;width:100%;height:100%;gap:1.5rem}.layout-players-left{flex-direction:row}.layout-players-left .players-sidebar{width:300px;min-width:300px;flex-shrink:0}.layout-players-left .soccer-board-container{flex:1;min-width:0}.layout-players-right{flex-direction:row-reverse}.layout-players-right .players-sidebar{width:300px;min-width:300px;flex-shrink:0}.layout-players-right .soccer-board-container{flex:1;min-width:0}.layout-players-top{flex-direction:column}.layout-players-top .players-sidebar{width:100%;max-height:300px}.layout-players-top .soccer-board-container{flex:1;min-height:0}.layout-players-bottom{flex-direction:column-reverse}.layout-players-bottom .players-sidebar{width:100%;max-height:300px}.layout-players-bottom .soccer-board-container{flex:1;min-height:0}.layout-no-sidebar .soccer-board-container{width:100%;height:100%}.players-sidebar{display:flex;flex-direction:column;background:transparent;padding:0;overflow-y:auto}.players-grid{display:grid;gap:.5rem;align-content:start}.soccer-board-container{width:100%;display:flex;justify-content:center;align-items:center;padding:var(--soccer-board-padding, 0);max-width:var(--soccer-board-max-width, none);width:var(--soccer-board-width, 100%);aspect-ratio:105/68}.field-container{display:flex;flex-direction:column;gap:0;width:100%;min-width:300px;height:auto}.field-container.landscape{flex-direction:row}.field-container.landscape .field-half{width:50%;aspect-ratio:52.5/68;background:repeating-linear-gradient(90deg,#2d5016 0px 10px,#3a6b1f 10px 20px)}.field-container.landscape .field-half.home{border-radius:0 10px 10px 0;box-shadow:10px 0 30px #0000004d}.field-container.landscape .field-half.away{border-radius:10px 0 0 10px;box-shadow:-5px 0 20px #0003}.field-container.landscape .field-half.home .center-border{inset:0 auto 0 0;width:3px;height:auto}.field-container.landscape .field-half.away .center-border{inset:0 0 0 auto;width:3px;height:auto}.field-container.landscape .field-half.home .half-circle{top:50%;left:0;width:17.4%;height:26.9%;transform:translateY(-50%);border:2px solid rgba(255,255,255,.5);border-left:none;border-top:2px solid rgba(255,255,255,.5);border-bottom:2px solid rgba(255,255,255,.5);border-radius:0 999px 999px 0}.field-container.landscape .field-half.away .half-circle{inset:50% 0 auto auto;width:17.4%;height:26.9%;transform:translateY(-50%);border:2px solid rgba(255,255,255,.5);border-right:none;border-top:2px solid rgba(255,255,255,.5);border-bottom:2px solid rgba(255,255,255,.5);border-radius:999px 0 0 999px}.field-container.landscape .field-half.home .penalty-area{inset:50% 0 auto auto;transform:translateY(-50%);height:59.3%;width:31.4%;border:2px solid rgba(255,255,255,.5);border-right:none}.field-container.landscape .field-half.away .penalty-area{inset:50% auto auto 0;transform:translateY(-50%);height:59.3%;width:31.4%;border:2px solid rgba(255,255,255,.5);border-left:none}.field-container.landscape .field-half.home .goal-area{inset:50% 0 auto auto;transform:translateY(-50%);height:26.9%;width:10.5%;border:2px solid rgba(255,255,255,.5);border-right:none}.field-container.landscape .field-half.away .goal-area{inset:50% auto auto 0;transform:translateY(-50%);height:26.9%;width:10.5%;border:2px solid rgba(255,255,255,.5);border-left:none}.field-half{position:relative;width:100%;aspect-ratio:68/52.5;background:repeating-linear-gradient(0deg,#2d5016 0px 10px,#3a6b1f 10px 20px);overflow:hidden}.field-half.home{border-radius:0 0 10px 10px;box-shadow:0 10px 30px #0000004d}.field-half.away{border-radius:10px 10px 0 0;box-shadow:0 -5px 20px #0003}.field-half.hidden{display:none}.field-lines{position:absolute;inset:0;pointer-events:none}.field-half.home .center-border{position:absolute;background:#fffc;z-index:10;top:0;left:0;right:0;height:3px}.field-half.away .center-border{position:absolute;background:#fffc;z-index:10;bottom:0;left:0;right:0;height:3px}.field-half.home .half-circle{position:absolute;z-index:5;top:0;left:50%;width:26.9%;height:17.4%;transform:translate(-50%);border:2px solid rgba(255,255,255,.5);border-top:none;border-radius:0 0 999px 999px;background:transparent}.field-half.away .half-circle{position:absolute;z-index:5;bottom:0;left:50%;width:26.9%;height:17.4%;transform:translate(-50%);border:2px solid rgba(255,255,255,.5);border-bottom:none;border-radius:999px 999px 0 0;background:transparent}.field-half.home .penalty-area{position:absolute;z-index:5;bottom:0;left:50%;transform:translate(-50%);width:59.3%;height:31.4%;border:2px solid rgba(255,255,255,.5);border-bottom:none}.field-half.away .penalty-area{position:absolute;z-index:5;top:0;left:50%;transform:translate(-50%);width:59.3%;height:31.4%;border:2px solid rgba(255,255,255,.5);border-top:none}.field-half.home .goal-area{position:absolute;z-index:5;bottom:0;left:50%;transform:translate(-50%);width:26.9%;height:10.5%;border:2px solid rgba(255,255,255,.5);border-bottom:none}.field-half.away .goal-area{position:absolute;z-index:5;top:0;left:50%;transform:translate(-50%);width:26.9%;height:10.5%;border:2px solid rgba(255,255,255,.5);border-top:none}.position-zones{position:absolute;inset:0;pointer-events:none;z-index:15}.position-zones.hidden{display:none}.position-zone{position:absolute;border:2px solid rgba(255,255,255,.8);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;color:#fff;text-shadow:0 2px 4px rgba(0,0,0,.8);opacity:.85;transition:all .3s ease}.position-zone:hover{opacity:1;z-index:100;transform:scale(1.05)}.players-container{position:absolute;inset:0;pointer-events:none;z-index:20}.players-container>*{pointer-events:auto}\n"] }]
|
|
961
1181
|
}], ctorParameters: () => [], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], teamSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "teamSide", required: false }] }], showPositions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPositions", required: false }] }], fit: [{ type: i0.Input, args: [{ isSignal: true, alias: "fit", required: false }] }], players: [{ type: i0.Input, args: [{ isSignal: true, alias: "players", required: false }] }], showPlayersSidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPlayersSidebar", required: false }] }], playersPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "playersPosition", required: false }] }], playersColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "playersColumns", required: false }] }], maxPlayersPerSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxPlayersPerSide", required: false }] }], fieldContainer: [{ type: i0.ViewChild, args: ['fieldContainer', { isSignal: true }] }], homeHalf: [{ type: i0.ViewChild, args: ['homeHalf', { isSignal: true }] }], awayHalf: [{ type: i0.ViewChild, args: ['awayHalf', { isSignal: true }] }], soccerBoardContainer: [{ type: i0.ViewChild, args: ['soccerBoardContainer', { isSignal: true }] }], playerPositioned: [{ type: i0.Output, args: ["playerPositioned"] }], playerRemovedFromField: [{ type: i0.Output, args: ["playerRemovedFromField"] }], imageExported: [{ type: i0.Output, args: ["imageExported"] }], playerClicked: [{ type: i0.Output, args: ["playerClicked"] }], hostWidthStyle: [{
|
|
962
1182
|
type: HostBinding,
|
|
963
1183
|
args: ['style.width']
|