@tolle_/tolle-ui 18.2.30 → 18.3.1
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/esm2022/lib/alert-dialog-dynamic.component.mjs +8 -3
- package/esm2022/lib/alert-dialog.component.mjs +39 -6
- package/esm2022/lib/alert-dialog.service.mjs +38 -6
- package/esm2022/lib/bar-list.component.mjs +95 -0
- package/esm2022/lib/category-bar.component.mjs +130 -0
- package/esm2022/lib/chart-spark.component.mjs +120 -0
- package/esm2022/lib/chart.component.mjs +131 -45
- package/esm2022/lib/chart.service.mjs +33 -8
- package/esm2022/lib/context-menu.service.mjs +11 -7
- package/esm2022/lib/modal-ref.mjs +36 -7
- package/esm2022/lib/modal.component.mjs +42 -28
- package/esm2022/lib/modal.mjs +2 -29
- package/esm2022/lib/modal.service.mjs +26 -4
- package/esm2022/lib/progress-circle.component.mjs +138 -0
- package/esm2022/lib/resizable-panel.component.mjs +5 -5
- package/esm2022/lib/segment.component.mjs +3 -3
- package/esm2022/lib/tracker.component.mjs +84 -0
- package/esm2022/public-api.mjs +6 -1
- package/fesm2022/tolle-ui.mjs +900 -139
- package/fesm2022/tolle-ui.mjs.map +1 -1
- package/lib/alert-dialog-dynamic.component.d.ts +5 -0
- package/lib/alert-dialog.component.d.ts +13 -0
- package/lib/bar-list.component.d.ts +39 -0
- package/lib/category-bar.component.d.ts +49 -0
- package/lib/chart-spark.component.d.ts +49 -0
- package/lib/chart.component.d.ts +68 -9
- package/lib/chart.service.d.ts +25 -5
- package/lib/modal-ref.d.ts +6 -1
- package/lib/modal.component.d.ts +7 -4
- package/lib/modal.d.ts +9 -3
- package/lib/progress-circle.component.d.ts +35 -0
- package/lib/tracker.component.d.ts +39 -0
- package/package.json +3 -2
- package/preset.js +7 -1
- package/public-api.d.ts +5 -0
- package/registry/docs-content.json +330 -2
- package/registry/llms-full.txt +98 -2
- package/registry/llms.txt +5 -0
- package/registry/manifest.json +380 -3
- package/registry/r/alert-dialog-dynamic.json +1 -1
- package/registry/r/alert-dialog.json +1 -1
- package/registry/r/bar-list.json +21 -0
- package/registry/r/category-bar.json +21 -0
- package/registry/r/chart-pie.json +1 -1
- package/registry/r/chart-spark.json +30 -0
- package/registry/r/chart.json +2 -2
- package/registry/r/context-menu-trigger.json +1 -1
- package/registry/r/context-menu.json +1 -1
- package/registry/r/modal.json +3 -3
- package/registry/r/progress-circle.json +21 -0
- package/registry/r/resizable-panel.json +1 -1
- package/registry/r/segment.json +1 -1
- package/registry/r/tracker.json +25 -0
- package/registry/registry.json +112 -0
- package/theme.css +15 -3
package/fesm2022/tolle-ui.mjs
CHANGED
|
@@ -6310,12 +6310,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6310
6310
|
args: [AccordionItemComponent]
|
|
6311
6311
|
}] } });
|
|
6312
6312
|
|
|
6313
|
+
/**
|
|
6314
|
+
* Safety net if the exit animation's `animationend` never fires — reduced
|
|
6315
|
+
* motion, no matching keyframe, or a panel that never rendered — so a modal
|
|
6316
|
+
* can never get stuck open forever.
|
|
6317
|
+
*/
|
|
6318
|
+
const CLOSE_FALLBACK_MS$1 = 300;
|
|
6313
6319
|
class ModalRef {
|
|
6314
6320
|
overlay;
|
|
6315
6321
|
modal;
|
|
6316
6322
|
stack;
|
|
6317
6323
|
_afterClosed$ = new Subject();
|
|
6318
6324
|
afterClosed$ = this._afterClosed$.asObservable();
|
|
6325
|
+
_closingSubject = new BehaviorSubject(false);
|
|
6326
|
+
/** True once `close()` has been called — drives the panel's exit animation. */
|
|
6327
|
+
closing$ = this._closingSubject.asObservable();
|
|
6319
6328
|
constructor(overlay, modal, stack) {
|
|
6320
6329
|
this.overlay = overlay;
|
|
6321
6330
|
this.modal = modal;
|
|
@@ -6329,22 +6338,46 @@ class ModalRef {
|
|
|
6329
6338
|
});
|
|
6330
6339
|
}
|
|
6331
6340
|
/**
|
|
6332
|
-
* Closes the modal
|
|
6341
|
+
* Closes the modal. Signals the panel and backdrop to play their exit
|
|
6342
|
+
* animation, then tears down the overlay once it finishes (or after
|
|
6343
|
+
* `CLOSE_FALLBACK_MS`, whichever comes first).
|
|
6333
6344
|
* @param result Data to pass back to the caller
|
|
6334
6345
|
*/
|
|
6335
6346
|
close(result) {
|
|
6336
|
-
this.
|
|
6337
|
-
|
|
6338
|
-
this.
|
|
6339
|
-
this.
|
|
6347
|
+
if (this._closingSubject.value)
|
|
6348
|
+
return;
|
|
6349
|
+
this._closingSubject.next(true);
|
|
6350
|
+
this.overlay.backdropElement?.setAttribute('data-state', 'closed');
|
|
6351
|
+
let finished = false;
|
|
6352
|
+
const finish = () => {
|
|
6353
|
+
if (finished)
|
|
6354
|
+
return;
|
|
6355
|
+
finished = true;
|
|
6356
|
+
this._afterClosed$.next(result);
|
|
6357
|
+
this._afterClosed$.complete();
|
|
6358
|
+
this.overlay.dispose();
|
|
6359
|
+
this.stack.unregister(this);
|
|
6360
|
+
};
|
|
6361
|
+
const panel = this.overlay.overlayElement.querySelector('[data-state]');
|
|
6362
|
+
if (panel) {
|
|
6363
|
+
panel.addEventListener('animationend', finish, { once: true });
|
|
6364
|
+
setTimeout(finish, CLOSE_FALLBACK_MS$1);
|
|
6365
|
+
}
|
|
6366
|
+
else {
|
|
6367
|
+
finish();
|
|
6368
|
+
}
|
|
6340
6369
|
}
|
|
6341
6370
|
}
|
|
6342
6371
|
|
|
6343
6372
|
class ModalComponent {
|
|
6344
6373
|
ref;
|
|
6374
|
+
cdr = inject(ChangeDetectorRef);
|
|
6375
|
+
subscription = new Subscription();
|
|
6345
6376
|
contentType = 'string';
|
|
6346
6377
|
content;
|
|
6347
6378
|
modalClasses = '';
|
|
6379
|
+
/** True while the exit animation plays, just before the overlay is torn down. */
|
|
6380
|
+
closing = false;
|
|
6348
6381
|
/** Stable, per-instance ids used to wire dialog ARIA relationships. */
|
|
6349
6382
|
_uid = Math.random().toString(36).substr(2, 9);
|
|
6350
6383
|
titleId = `tolle-modal-title-${this._uid}`;
|
|
@@ -6356,16 +6389,21 @@ class ModalComponent {
|
|
|
6356
6389
|
this.content = this.ref.modal.content;
|
|
6357
6390
|
this.modalClasses = this.getModalSizeCss();
|
|
6358
6391
|
this.determineContentType();
|
|
6392
|
+
this.subscription.add(this.ref.closing$.subscribe((closing) => {
|
|
6393
|
+
this.closing = closing;
|
|
6394
|
+
this.cdr.markForCheck();
|
|
6395
|
+
}));
|
|
6359
6396
|
}
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
return !!(this.ref.modal.showCloseButton || this.ref.modal.title);
|
|
6397
|
+
ngOnDestroy() {
|
|
6398
|
+
this.subscription.unsubscribe();
|
|
6363
6399
|
}
|
|
6364
6400
|
getModalSizeCss() {
|
|
6365
6401
|
const { size } = this.ref.modal;
|
|
6366
6402
|
return cn(
|
|
6367
6403
|
// Surface: overlay card that never exceeds the viewport (header stays, body scrolls).
|
|
6368
|
-
'
|
|
6404
|
+
'relative flex w-full mx-auto flex-col gap-4 rounded-lg border border-border bg-background p-6 text-foreground shadow-lg duration-200',
|
|
6405
|
+
// Enter/exit animation, driven by `data-state` (see `closing` above).
|
|
6406
|
+
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95', size === 'fullscreen' ? 'h-screen w-screen rounded-none' : 'max-h-[85vh]',
|
|
6369
6407
|
// Sizing scale with explicit max-widths
|
|
6370
6408
|
size === 'xs' && 'max-w-[320px]', size === 'sm' && 'max-w-[425px]', size === 'default' && 'max-w-[512px]', size === 'lg' && 'max-w-[1024px]', size === 'xl' && 'max-w-[1280px]');
|
|
6371
6409
|
}
|
|
@@ -6383,6 +6421,7 @@ class ModalComponent {
|
|
|
6383
6421
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalComponent, deps: [{ token: ModalRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
6384
6422
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ModalComponent, isStandalone: true, selector: "tolle-modal", ngImport: i0, template: `
|
|
6385
6423
|
<div [class]="modalClasses" class="pointer-events-auto"
|
|
6424
|
+
[attr.data-state]="closing ? 'closed' : 'open'"
|
|
6386
6425
|
role="dialog"
|
|
6387
6426
|
aria-modal="true"
|
|
6388
6427
|
[attr.aria-labelledby]="ref.modal.title ? titleId : null"
|
|
@@ -6390,25 +6429,26 @@ class ModalComponent {
|
|
|
6390
6429
|
cdkTrapFocus cdkTrapFocusAutoCapture
|
|
6391
6430
|
(mousedown)="$event.stopPropagation()">
|
|
6392
6431
|
|
|
6432
|
+
<button
|
|
6433
|
+
*ngIf="ref.modal.showCloseButton"
|
|
6434
|
+
type="button"
|
|
6435
|
+
(click)="ref.close()"
|
|
6436
|
+
aria-label="Close"
|
|
6437
|
+
class="absolute right-4 top-4 grid h-6 w-6 place-items-center rounded-sm text-muted-foreground opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
|
|
6438
|
+
<i class="ri-close-line text-base" aria-hidden="true"></i>
|
|
6439
|
+
</button>
|
|
6440
|
+
|
|
6393
6441
|
<!-- Header -->
|
|
6394
|
-
<div *ngIf="
|
|
6395
|
-
<h2
|
|
6442
|
+
<div *ngIf="ref.modal.title" class="flex shrink-0 flex-col gap-1.5 pr-8 text-center sm:text-left">
|
|
6443
|
+
<h2 [id]="titleId" class="text-lg font-semibold leading-none tracking-tight text-foreground">
|
|
6396
6444
|
{{ ref.modal.title }}
|
|
6397
6445
|
</h2>
|
|
6398
|
-
<button
|
|
6399
|
-
*ngIf="ref.modal.showCloseButton"
|
|
6400
|
-
type="button"
|
|
6401
|
-
(click)="ref.close()"
|
|
6402
|
-
aria-label="Close"
|
|
6403
|
-
class="-mr-2 -mt-2 ml-auto grid h-8 w-8 shrink-0 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
6404
|
-
<i class="ri-close-line text-xl"></i>
|
|
6405
|
-
</button>
|
|
6406
6446
|
</div>
|
|
6407
6447
|
|
|
6408
6448
|
<!-- Body -->
|
|
6409
6449
|
<div class="min-h-0 flex-1 overflow-y-auto">
|
|
6410
6450
|
<ng-container [ngSwitch]="contentType">
|
|
6411
|
-
<p *ngSwitchCase="'string'" [id]="descId"
|
|
6451
|
+
<p *ngSwitchCase="'string'" [id]="descId" class="text-sm leading-relaxed text-muted-foreground">{{ content }}</p>
|
|
6412
6452
|
|
|
6413
6453
|
<ng-container *ngSwitchCase="'template'">
|
|
6414
6454
|
<ng-container *ngTemplateOutlet="asTemplate; context: ref.modal.context"></ng-container>
|
|
@@ -6426,6 +6466,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6426
6466
|
type: Component,
|
|
6427
6467
|
args: [{ selector: 'tolle-modal', standalone: true, imports: [CommonModule, A11yModule], template: `
|
|
6428
6468
|
<div [class]="modalClasses" class="pointer-events-auto"
|
|
6469
|
+
[attr.data-state]="closing ? 'closed' : 'open'"
|
|
6429
6470
|
role="dialog"
|
|
6430
6471
|
aria-modal="true"
|
|
6431
6472
|
[attr.aria-labelledby]="ref.modal.title ? titleId : null"
|
|
@@ -6433,25 +6474,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6433
6474
|
cdkTrapFocus cdkTrapFocusAutoCapture
|
|
6434
6475
|
(mousedown)="$event.stopPropagation()">
|
|
6435
6476
|
|
|
6477
|
+
<button
|
|
6478
|
+
*ngIf="ref.modal.showCloseButton"
|
|
6479
|
+
type="button"
|
|
6480
|
+
(click)="ref.close()"
|
|
6481
|
+
aria-label="Close"
|
|
6482
|
+
class="absolute right-4 top-4 grid h-6 w-6 place-items-center rounded-sm text-muted-foreground opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
|
|
6483
|
+
<i class="ri-close-line text-base" aria-hidden="true"></i>
|
|
6484
|
+
</button>
|
|
6485
|
+
|
|
6436
6486
|
<!-- Header -->
|
|
6437
|
-
<div *ngIf="
|
|
6438
|
-
<h2
|
|
6487
|
+
<div *ngIf="ref.modal.title" class="flex shrink-0 flex-col gap-1.5 pr-8 text-center sm:text-left">
|
|
6488
|
+
<h2 [id]="titleId" class="text-lg font-semibold leading-none tracking-tight text-foreground">
|
|
6439
6489
|
{{ ref.modal.title }}
|
|
6440
6490
|
</h2>
|
|
6441
|
-
<button
|
|
6442
|
-
*ngIf="ref.modal.showCloseButton"
|
|
6443
|
-
type="button"
|
|
6444
|
-
(click)="ref.close()"
|
|
6445
|
-
aria-label="Close"
|
|
6446
|
-
class="-mr-2 -mt-2 ml-auto grid h-8 w-8 shrink-0 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
6447
|
-
<i class="ri-close-line text-xl"></i>
|
|
6448
|
-
</button>
|
|
6449
6491
|
</div>
|
|
6450
6492
|
|
|
6451
6493
|
<!-- Body -->
|
|
6452
6494
|
<div class="min-h-0 flex-1 overflow-y-auto">
|
|
6453
6495
|
<ng-container [ngSwitch]="contentType">
|
|
6454
|
-
<p *ngSwitchCase="'string'" [id]="descId"
|
|
6496
|
+
<p *ngSwitchCase="'string'" [id]="descId" class="text-sm leading-relaxed text-muted-foreground">{{ content }}</p>
|
|
6455
6497
|
|
|
6456
6498
|
<ng-container *ngSwitchCase="'template'">
|
|
6457
6499
|
<ng-container *ngTemplateOutlet="asTemplate; context: ref.modal.context"></ng-container>
|
|
@@ -6490,6 +6532,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6490
6532
|
args: [{ providedIn: 'root' }]
|
|
6491
6533
|
}] });
|
|
6492
6534
|
|
|
6535
|
+
/**
|
|
6536
|
+
* `open()` always receives a plain object literal, never a `new Modal()`
|
|
6537
|
+
* instance, so a class field initializer would never actually apply — these
|
|
6538
|
+
* are the real defaults, merged in explicitly below.
|
|
6539
|
+
*/
|
|
6540
|
+
const MODAL_DEFAULTS = {
|
|
6541
|
+
size: 'default',
|
|
6542
|
+
backdropClose: true,
|
|
6543
|
+
showCloseButton: true,
|
|
6544
|
+
};
|
|
6493
6545
|
class ModalService {
|
|
6494
6546
|
overlay;
|
|
6495
6547
|
injector;
|
|
@@ -6500,12 +6552,13 @@ class ModalService {
|
|
|
6500
6552
|
this.stack = stack;
|
|
6501
6553
|
}
|
|
6502
6554
|
open(config) {
|
|
6555
|
+
const resolved = { ...MODAL_DEFAULTS, ...config };
|
|
6503
6556
|
// 0. Remember what was focused so we can restore it after the modal closes.
|
|
6504
6557
|
const previouslyFocused = document.activeElement;
|
|
6505
6558
|
// 1. Create the Overlay (DOM placeholder)
|
|
6506
6559
|
const overlayRef = this.createOverlay();
|
|
6507
6560
|
// 2. Create the Controller (Ref)
|
|
6508
|
-
const modalRef = new ModalRef(overlayRef,
|
|
6561
|
+
const modalRef = new ModalRef(overlayRef, resolved, this.stack);
|
|
6509
6562
|
// 3. Create Injector to allow the Component to access the Ref
|
|
6510
6563
|
const injector = Injector.create({
|
|
6511
6564
|
parent: this.injector,
|
|
@@ -6514,7 +6567,7 @@ class ModalService {
|
|
|
6514
6567
|
// Escape-to-close: mirror the backdrop close path, but only for
|
|
6515
6568
|
// non-blocking modals (backdropClose !== false).
|
|
6516
6569
|
overlayRef.keydownEvents().subscribe((event) => {
|
|
6517
|
-
if (event.key === 'Escape' &&
|
|
6570
|
+
if (event.key === 'Escape' && resolved.backdropClose) {
|
|
6518
6571
|
event.preventDefault();
|
|
6519
6572
|
modalRef.close();
|
|
6520
6573
|
}
|
|
@@ -6524,6 +6577,9 @@ class ModalService {
|
|
|
6524
6577
|
// 4. Attach the UI Component to the Overlay
|
|
6525
6578
|
const portal = new ComponentPortal(ModalComponent, null, injector);
|
|
6526
6579
|
overlayRef.attach(portal);
|
|
6580
|
+
// The backdrop element only exists once attached — setting this any
|
|
6581
|
+
// earlier (e.g. in ModalRef's constructor) would silently no-op.
|
|
6582
|
+
overlayRef.backdropElement?.setAttribute('data-state', 'open');
|
|
6527
6583
|
return modalRef;
|
|
6528
6584
|
}
|
|
6529
6585
|
/** Global helper to close everything */
|
|
@@ -6534,7 +6590,15 @@ class ModalService {
|
|
|
6534
6590
|
createOverlay() {
|
|
6535
6591
|
const config = new OverlayConfig({
|
|
6536
6592
|
hasBackdrop: true,
|
|
6537
|
-
backdropClass: [
|
|
6593
|
+
backdropClass: [
|
|
6594
|
+
'cdk-overlay-backdrop',
|
|
6595
|
+
'bg-black/80',
|
|
6596
|
+
'backdrop-blur-sm',
|
|
6597
|
+
'data-[state=open]:animate-in',
|
|
6598
|
+
'data-[state=open]:fade-in-0',
|
|
6599
|
+
'data-[state=closed]:animate-out',
|
|
6600
|
+
'data-[state=closed]:fade-out-0',
|
|
6601
|
+
],
|
|
6538
6602
|
panelClass: [
|
|
6539
6603
|
'w-full',
|
|
6540
6604
|
'h-full',
|
|
@@ -6559,35 +6623,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6559
6623
|
args: [{ providedIn: 'root' }]
|
|
6560
6624
|
}], ctorParameters: () => [{ type: i1$2.Overlay }, { type: i0.Injector }, { type: ModalStackService }] });
|
|
6561
6625
|
|
|
6562
|
-
class Modal {
|
|
6563
|
-
/** The content to display (String, Component, or Template) */
|
|
6564
|
-
content;
|
|
6565
|
-
/** Optional title for the standard header */
|
|
6566
|
-
title;
|
|
6567
|
-
/** * Predefined size scale.
|
|
6568
|
-
* - xs: 320px (Mobile alerts)
|
|
6569
|
-
* - sm: 425px (Standard dialogs)
|
|
6570
|
-
* - default: 544px (Forms)
|
|
6571
|
-
* - lg: 90% / 1024px (Data tables)
|
|
6572
|
-
* - xl: 1280px (Large forms, dashboards)
|
|
6573
|
-
* - fullscreen: 100vw/100vh (Complex workflows)
|
|
6574
|
-
*/
|
|
6575
|
-
size = 'default';
|
|
6576
|
-
/** * If true (default), clicking the backdrop closes the modal.
|
|
6577
|
-
* Set to false for "blocking" modals (e.g., Terms of Service).
|
|
6578
|
-
*/
|
|
6579
|
-
backdropClose = true;
|
|
6580
|
-
/** * Data to pass to a Component content.
|
|
6581
|
-
* Accessed via @Input() in the child component.
|
|
6582
|
-
*/
|
|
6583
|
-
data;
|
|
6584
|
-
/** * Context to pass to a TemplateRef content.
|
|
6585
|
-
* Accessed via `let-val` in the HTML.
|
|
6586
|
-
*/
|
|
6587
|
-
context;
|
|
6588
|
-
showCloseButton = true;
|
|
6589
|
-
}
|
|
6590
|
-
|
|
6591
6626
|
class ButtonGroupComponent {
|
|
6592
6627
|
class = '';
|
|
6593
6628
|
cn = cn;
|
|
@@ -9071,7 +9106,7 @@ class SegmentComponent {
|
|
|
9071
9106
|
(keydown)="onKeydown($event)"
|
|
9072
9107
|
>
|
|
9073
9108
|
<div
|
|
9074
|
-
class="absolute top-1 bottom-1 bg-primary shadow-sm rounded-md transition-all duration-300 ease-
|
|
9109
|
+
class="absolute top-1 bottom-1 bg-primary shadow-sm rounded-md transition-all duration-300 ease-tolle"
|
|
9075
9110
|
[style.left.px]="gliderLeft"
|
|
9076
9111
|
[style.width.px]="gliderWidth"
|
|
9077
9112
|
[class.opacity-0]="!hasValue"
|
|
@@ -9128,7 +9163,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
9128
9163
|
(keydown)="onKeydown($event)"
|
|
9129
9164
|
>
|
|
9130
9165
|
<div
|
|
9131
|
-
class="absolute top-1 bottom-1 bg-primary shadow-sm rounded-md transition-all duration-300 ease-
|
|
9166
|
+
class="absolute top-1 bottom-1 bg-primary shadow-sm rounded-md transition-all duration-300 ease-tolle"
|
|
9132
9167
|
[style.left.px]="gliderLeft"
|
|
9133
9168
|
[style.width.px]="gliderWidth"
|
|
9134
9169
|
[class.opacity-0]="!hasValue"
|
|
@@ -10024,6 +10059,140 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
10024
10059
|
type: Input
|
|
10025
10060
|
}] } });
|
|
10026
10061
|
|
|
10062
|
+
/**
|
|
10063
|
+
* Radial counterpart to `tolle-progress`: a ring that fills clockwise from
|
|
10064
|
+
* 12 o'clock. Project content to label the centre — a percentage, an icon,
|
|
10065
|
+
* whatever the caller wants; the ring renders nothing there on its own.
|
|
10066
|
+
* @new
|
|
10067
|
+
*/
|
|
10068
|
+
class ProgressCircleComponent {
|
|
10069
|
+
/**
|
|
10070
|
+
* Angular writes a bound `class` input through its styling path, which does
|
|
10071
|
+
* not mark an OnPush component dirty — without this hook the component keeps
|
|
10072
|
+
* rendering the class it was born with.
|
|
10073
|
+
*/
|
|
10074
|
+
ngOnChanges() {
|
|
10075
|
+
this.cdr.markForCheck();
|
|
10076
|
+
}
|
|
10077
|
+
/** Percent complete, 0-100. @default 0 */
|
|
10078
|
+
value = 0;
|
|
10079
|
+
/** Diameter of the ring in px. @default 120 */
|
|
10080
|
+
size = 120;
|
|
10081
|
+
/** Thickness of the ring in px. @default 8 */
|
|
10082
|
+
strokeWidth = 8;
|
|
10083
|
+
/** Extra Tailwind classes merged onto the ring via `cn()` (last-wins). */
|
|
10084
|
+
class = '';
|
|
10085
|
+
cdr = inject(ChangeDetectorRef);
|
|
10086
|
+
get clampedValue() {
|
|
10087
|
+
return Math.min(100, Math.max(0, this.value ?? 0));
|
|
10088
|
+
}
|
|
10089
|
+
get viewBox() {
|
|
10090
|
+
return '0 0 ' + this.size + ' ' + this.size;
|
|
10091
|
+
}
|
|
10092
|
+
get center() {
|
|
10093
|
+
return this.size / 2;
|
|
10094
|
+
}
|
|
10095
|
+
/** Leaves half the stroke width as margin so the ring never clips the frame. */
|
|
10096
|
+
get radius() {
|
|
10097
|
+
return Math.max(0, (this.size - this.strokeWidth) / 2);
|
|
10098
|
+
}
|
|
10099
|
+
get circumference() {
|
|
10100
|
+
return 2 * Math.PI * this.radius;
|
|
10101
|
+
}
|
|
10102
|
+
get dashOffset() {
|
|
10103
|
+
return this.circumference * (1 - this.clampedValue / 100);
|
|
10104
|
+
}
|
|
10105
|
+
get computedClass() {
|
|
10106
|
+
return cn('relative inline-flex shrink-0 items-center justify-center', this.class);
|
|
10107
|
+
}
|
|
10108
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProgressCircleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10109
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ProgressCircleComponent, isStandalone: true, selector: "tolle-progress-circle", inputs: { value: "value", size: "size", strokeWidth: "strokeWidth", class: "class" }, usesOnChanges: true, ngImport: i0, template: `
|
|
10110
|
+
<div
|
|
10111
|
+
[class]="computedClass"
|
|
10112
|
+
[style.width.px]="size"
|
|
10113
|
+
[style.height.px]="size"
|
|
10114
|
+
role="progressbar"
|
|
10115
|
+
[attr.aria-valuemin]="0"
|
|
10116
|
+
[attr.aria-valuemax]="100"
|
|
10117
|
+
[attr.aria-valuenow]="clampedValue"
|
|
10118
|
+
>
|
|
10119
|
+
<svg [attr.width]="size" [attr.height]="size" [attr.viewBox]="viewBox" class="block">
|
|
10120
|
+
<svg:circle
|
|
10121
|
+
[attr.cx]="center"
|
|
10122
|
+
[attr.cy]="center"
|
|
10123
|
+
[attr.r]="radius"
|
|
10124
|
+
fill="none"
|
|
10125
|
+
[attr.stroke-width]="strokeWidth"
|
|
10126
|
+
class="stroke-primary/20"
|
|
10127
|
+
></svg:circle>
|
|
10128
|
+
<svg:circle
|
|
10129
|
+
[attr.cx]="center"
|
|
10130
|
+
[attr.cy]="center"
|
|
10131
|
+
[attr.r]="radius"
|
|
10132
|
+
fill="none"
|
|
10133
|
+
[attr.stroke-width]="strokeWidth"
|
|
10134
|
+
stroke-linecap="round"
|
|
10135
|
+
[attr.stroke-dasharray]="circumference"
|
|
10136
|
+
[attr.stroke-dashoffset]="dashOffset"
|
|
10137
|
+
[attr.transform]="'rotate(-90 ' + center + ' ' + center + ')'"
|
|
10138
|
+
class="stroke-primary transition-[stroke-dashoffset] duration-300 ease-in-out"
|
|
10139
|
+
></svg:circle>
|
|
10140
|
+
</svg>
|
|
10141
|
+
<div class="absolute inset-0 flex items-center justify-center text-sm font-medium tabular-nums text-foreground">
|
|
10142
|
+
<ng-content></ng-content>
|
|
10143
|
+
</div>
|
|
10144
|
+
</div>
|
|
10145
|
+
`, isInline: true, styles: [":host{display:inline-flex}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10146
|
+
}
|
|
10147
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProgressCircleComponent, decorators: [{
|
|
10148
|
+
type: Component,
|
|
10149
|
+
args: [{ selector: 'tolle-progress-circle', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
10150
|
+
<div
|
|
10151
|
+
[class]="computedClass"
|
|
10152
|
+
[style.width.px]="size"
|
|
10153
|
+
[style.height.px]="size"
|
|
10154
|
+
role="progressbar"
|
|
10155
|
+
[attr.aria-valuemin]="0"
|
|
10156
|
+
[attr.aria-valuemax]="100"
|
|
10157
|
+
[attr.aria-valuenow]="clampedValue"
|
|
10158
|
+
>
|
|
10159
|
+
<svg [attr.width]="size" [attr.height]="size" [attr.viewBox]="viewBox" class="block">
|
|
10160
|
+
<svg:circle
|
|
10161
|
+
[attr.cx]="center"
|
|
10162
|
+
[attr.cy]="center"
|
|
10163
|
+
[attr.r]="radius"
|
|
10164
|
+
fill="none"
|
|
10165
|
+
[attr.stroke-width]="strokeWidth"
|
|
10166
|
+
class="stroke-primary/20"
|
|
10167
|
+
></svg:circle>
|
|
10168
|
+
<svg:circle
|
|
10169
|
+
[attr.cx]="center"
|
|
10170
|
+
[attr.cy]="center"
|
|
10171
|
+
[attr.r]="radius"
|
|
10172
|
+
fill="none"
|
|
10173
|
+
[attr.stroke-width]="strokeWidth"
|
|
10174
|
+
stroke-linecap="round"
|
|
10175
|
+
[attr.stroke-dasharray]="circumference"
|
|
10176
|
+
[attr.stroke-dashoffset]="dashOffset"
|
|
10177
|
+
[attr.transform]="'rotate(-90 ' + center + ' ' + center + ')'"
|
|
10178
|
+
class="stroke-primary transition-[stroke-dashoffset] duration-300 ease-in-out"
|
|
10179
|
+
></svg:circle>
|
|
10180
|
+
</svg>
|
|
10181
|
+
<div class="absolute inset-0 flex items-center justify-center text-sm font-medium tabular-nums text-foreground">
|
|
10182
|
+
<ng-content></ng-content>
|
|
10183
|
+
</div>
|
|
10184
|
+
</div>
|
|
10185
|
+
`, styles: [":host{display:inline-flex}\n"] }]
|
|
10186
|
+
}], propDecorators: { value: [{
|
|
10187
|
+
type: Input
|
|
10188
|
+
}], size: [{
|
|
10189
|
+
type: Input
|
|
10190
|
+
}], strokeWidth: [{
|
|
10191
|
+
type: Input
|
|
10192
|
+
}], class: [{
|
|
10193
|
+
type: Input
|
|
10194
|
+
}] } });
|
|
10195
|
+
|
|
10027
10196
|
class SliderComponent {
|
|
10028
10197
|
min = 0;
|
|
10029
10198
|
max = 100;
|
|
@@ -11177,6 +11346,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
11177
11346
|
type: Input
|
|
11178
11347
|
}] } });
|
|
11179
11348
|
|
|
11349
|
+
/**
|
|
11350
|
+
* Safety net if the exit animation's `animationend` never fires — reduced
|
|
11351
|
+
* motion, no matching keyframe, or a panel that never rendered — so an
|
|
11352
|
+
* alert dialog can never get stuck open forever. Also used by
|
|
11353
|
+
* `alert-dialog.service.ts` and `alert-dialog-dynamic.component.ts`, which
|
|
11354
|
+
* animate the same content component through a different overlay wiring.
|
|
11355
|
+
*/
|
|
11356
|
+
const CLOSE_FALLBACK_MS = 300;
|
|
11180
11357
|
class AlertDialogInternalService {
|
|
11181
11358
|
openSubject = new BehaviorSubject(false);
|
|
11182
11359
|
open$ = this.openSubject.asObservable();
|
|
@@ -11271,14 +11448,39 @@ class AlertDialogPortalComponent {
|
|
|
11271
11448
|
});
|
|
11272
11449
|
const portal = new TemplatePortal(this.portalContent, this.viewContainerRef);
|
|
11273
11450
|
this.overlayRef.attach(portal);
|
|
11451
|
+
// The backdrop element only exists once attached — setting this any
|
|
11452
|
+
// earlier would silently no-op.
|
|
11453
|
+
this.overlayRef.backdropElement?.setAttribute('data-state', 'open');
|
|
11274
11454
|
}
|
|
11455
|
+
/**
|
|
11456
|
+
* Tears the overlay down once its exit animation finishes (or after
|
|
11457
|
+
* `CLOSE_FALLBACK_MS`, whichever comes first) instead of instantly, so
|
|
11458
|
+
* `data-[state=closed]:animate-out` actually gets a chance to play.
|
|
11459
|
+
*/
|
|
11275
11460
|
hide() {
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11461
|
+
const ref = this.overlayRef;
|
|
11462
|
+
if (!ref)
|
|
11463
|
+
return;
|
|
11464
|
+
ref.backdropElement?.setAttribute('data-state', 'closed');
|
|
11465
|
+
let finished = false;
|
|
11466
|
+
const finish = () => {
|
|
11467
|
+
if (finished)
|
|
11468
|
+
return;
|
|
11469
|
+
finished = true;
|
|
11470
|
+
ref.detach();
|
|
11471
|
+
ref.dispose();
|
|
11472
|
+
if (this.overlayRef === ref)
|
|
11473
|
+
this.overlayRef = undefined;
|
|
11280
11474
|
// Restore focus to the trigger.
|
|
11281
11475
|
this.previouslyFocused?.focus?.();
|
|
11476
|
+
};
|
|
11477
|
+
const panel = ref.overlayElement.querySelector('[data-state]');
|
|
11478
|
+
if (panel) {
|
|
11479
|
+
panel.addEventListener('animationend', finish, { once: true });
|
|
11480
|
+
setTimeout(finish, CLOSE_FALLBACK_MS);
|
|
11481
|
+
}
|
|
11482
|
+
else {
|
|
11483
|
+
finish();
|
|
11282
11484
|
}
|
|
11283
11485
|
}
|
|
11284
11486
|
ngOnDestroy() {
|
|
@@ -11316,7 +11518,7 @@ class AlertDialogContentComponent {
|
|
|
11316
11518
|
});
|
|
11317
11519
|
}
|
|
11318
11520
|
get computedClass() {
|
|
11319
|
-
return cn("fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50 grid w-full gap-4 border border-input bg-background p-6 shadow-lg data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] rounded-lg", {
|
|
11521
|
+
return cn("fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50 grid w-full gap-4 border border-input bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] rounded-lg", {
|
|
11320
11522
|
'max-w-xs': this.size === 'xs',
|
|
11321
11523
|
'max-w-sm': this.size === 'sm',
|
|
11322
11524
|
'max-w-md': this.size === 'md',
|
|
@@ -11479,6 +11681,11 @@ class AlertDialogRef {
|
|
|
11479
11681
|
class AlertDialogDynamicComponent {
|
|
11480
11682
|
config;
|
|
11481
11683
|
dialogRef;
|
|
11684
|
+
/**
|
|
11685
|
+
* Set by `AlertDialogService` once the dialog's result is ready, so the
|
|
11686
|
+
* content panel plays its exit animation before the overlay is torn down.
|
|
11687
|
+
*/
|
|
11688
|
+
closing = false;
|
|
11482
11689
|
onOpenChange(open) {
|
|
11483
11690
|
if (!open) {
|
|
11484
11691
|
this.close(false);
|
|
@@ -11489,7 +11696,7 @@ class AlertDialogDynamicComponent {
|
|
|
11489
11696
|
}
|
|
11490
11697
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AlertDialogDynamicComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11491
11698
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: AlertDialogDynamicComponent, isStandalone: true, selector: "tolle-alert-dialog-dynamic", ngImport: i0, template: `
|
|
11492
|
-
<tolle-alert-dialog [open]="
|
|
11699
|
+
<tolle-alert-dialog [open]="!closing" (openChange)="onOpenChange($event)">
|
|
11493
11700
|
<tolle-alert-dialog-content [size]="config.size || 'lg'">
|
|
11494
11701
|
<tolle-alert-dialog-header>
|
|
11495
11702
|
<tolle-alert-dialog-title>{{ config.title }}</tolle-alert-dialog-title>
|
|
@@ -11525,7 +11732,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
11525
11732
|
AlertDialogActionComponent,
|
|
11526
11733
|
ButtonComponent
|
|
11527
11734
|
], template: `
|
|
11528
|
-
<tolle-alert-dialog [open]="
|
|
11735
|
+
<tolle-alert-dialog [open]="!closing" (openChange)="onOpenChange($event)">
|
|
11529
11736
|
<tolle-alert-dialog-content [size]="config.size || 'lg'">
|
|
11530
11737
|
<tolle-alert-dialog-header>
|
|
11531
11738
|
<tolle-alert-dialog-title>{{ config.title }}</tolle-alert-dialog-title>
|
|
@@ -11556,7 +11763,15 @@ class AlertDialogService {
|
|
|
11556
11763
|
const previouslyFocused = document.activeElement;
|
|
11557
11764
|
const overlayConfig = new OverlayConfig({
|
|
11558
11765
|
hasBackdrop: true,
|
|
11559
|
-
backdropClass: [
|
|
11766
|
+
backdropClass: [
|
|
11767
|
+
'cdk-overlay-backdrop',
|
|
11768
|
+
'bg-black/80',
|
|
11769
|
+
'backdrop-blur-sm',
|
|
11770
|
+
'data-[state=open]:animate-in',
|
|
11771
|
+
'data-[state=open]:fade-in-0',
|
|
11772
|
+
'data-[state=closed]:animate-out',
|
|
11773
|
+
'data-[state=closed]:fade-out-0',
|
|
11774
|
+
],
|
|
11560
11775
|
positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically(),
|
|
11561
11776
|
scrollStrategy: this.overlay.scrollStrategies.block()
|
|
11562
11777
|
});
|
|
@@ -11564,13 +11779,36 @@ class AlertDialogService {
|
|
|
11564
11779
|
const dialogRef = new AlertDialogRef();
|
|
11565
11780
|
const portal = new ComponentPortal(AlertDialogDynamicComponent, null, this.injector);
|
|
11566
11781
|
const componentRef = overlayRef.attach(portal);
|
|
11782
|
+
// The backdrop element only exists once attached — setting this any
|
|
11783
|
+
// earlier would silently no-op.
|
|
11784
|
+
overlayRef.backdropElement?.setAttribute('data-state', 'open');
|
|
11567
11785
|
componentRef.instance.config = config;
|
|
11568
11786
|
componentRef.instance.dialogRef = dialogRef;
|
|
11787
|
+
// The result (`afterClosed$`) is ready immediately, but disposing the
|
|
11788
|
+
// overlay waits for the content panel's exit animation to finish (or
|
|
11789
|
+
// a fallback timeout), so `data-[state=closed]:animate-out` gets a
|
|
11790
|
+
// chance to play instead of being torn down mid-frame.
|
|
11569
11791
|
dialogRef.afterClosed$.subscribe(() => {
|
|
11570
|
-
overlayRef.
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11792
|
+
overlayRef.backdropElement?.setAttribute('data-state', 'closed');
|
|
11793
|
+
componentRef.instance.closing = true;
|
|
11794
|
+
let finished = false;
|
|
11795
|
+
const finish = () => {
|
|
11796
|
+
if (finished)
|
|
11797
|
+
return;
|
|
11798
|
+
finished = true;
|
|
11799
|
+
overlayRef.detach();
|
|
11800
|
+
overlayRef.dispose();
|
|
11801
|
+
// Restore focus to the trigger.
|
|
11802
|
+
previouslyFocused?.focus?.();
|
|
11803
|
+
};
|
|
11804
|
+
const panel = overlayRef.overlayElement.querySelector('[data-state]');
|
|
11805
|
+
if (panel) {
|
|
11806
|
+
panel.addEventListener('animationend', finish, { once: true });
|
|
11807
|
+
setTimeout(finish, CLOSE_FALLBACK_MS);
|
|
11808
|
+
}
|
|
11809
|
+
else {
|
|
11810
|
+
finish();
|
|
11811
|
+
}
|
|
11574
11812
|
});
|
|
11575
11813
|
// Alert-dialog semantics: backdrop clicks must NOT dismiss.
|
|
11576
11814
|
// Escape still closes the dialog.
|
|
@@ -16497,21 +16735,21 @@ class ResizablePanelComponent {
|
|
|
16497
16735
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ResizablePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
16498
16736
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ResizablePanelComponent, isStandalone: true, selector: "tolle-resizable-panel", inputs: { direction: "direction", class: "class" }, queries: [{ propertyName: "panels", predicate: i0.forwardRef(() => ResizablePanelItemComponent) }], ngImport: i0, template: `
|
|
16499
16737
|
<div [class]="computedContainerClass">
|
|
16500
|
-
<div class="flex" [class.flex-col]="direction === 'vertical'" [class.flex-row]="direction === 'horizontal'">
|
|
16738
|
+
<div class="flex h-full w-full" [class.flex-col]="direction === 'vertical'" [class.flex-row]="direction === 'horizontal'">
|
|
16501
16739
|
<ng-content></ng-content>
|
|
16502
16740
|
</div>
|
|
16503
16741
|
</div>
|
|
16504
|
-
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
16742
|
+
`, isInline: true, styles: [":host{display:block;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
16505
16743
|
}
|
|
16506
16744
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ResizablePanelComponent, decorators: [{
|
|
16507
16745
|
type: Component,
|
|
16508
16746
|
args: [{ selector: 'tolle-resizable-panel', standalone: true, imports: [CommonModule], template: `
|
|
16509
16747
|
<div [class]="computedContainerClass">
|
|
16510
|
-
<div class="flex" [class.flex-col]="direction === 'vertical'" [class.flex-row]="direction === 'horizontal'">
|
|
16748
|
+
<div class="flex h-full w-full" [class.flex-col]="direction === 'vertical'" [class.flex-row]="direction === 'horizontal'">
|
|
16511
16749
|
<ng-content></ng-content>
|
|
16512
16750
|
</div>
|
|
16513
16751
|
</div>
|
|
16514
|
-
`, styles: [":host{display:block}\n"] }]
|
|
16752
|
+
`, styles: [":host{display:block;width:100%;height:100%}\n"] }]
|
|
16515
16753
|
}], propDecorators: { direction: [{
|
|
16516
16754
|
type: Input
|
|
16517
16755
|
}], class: [{
|
|
@@ -16915,11 +17153,16 @@ class ContextMenuService {
|
|
|
16915
17153
|
})
|
|
16916
17154
|
]
|
|
16917
17155
|
});
|
|
17156
|
+
// Position via left/top, not transform: the menu's own
|
|
17157
|
+
// data-[state=open]:zoom-in-95/slide-in-from-top-2 enter animation also
|
|
17158
|
+
// drives `transform`, and a CSS animation overrides whatever an inline
|
|
17159
|
+
// transform says for as long as it's running — so a translate() here
|
|
17160
|
+
// would get clobbered by the animation and only "snap" into place once
|
|
17161
|
+
// it finishes, which is exactly the top-left-then-jump flash this avoids.
|
|
16918
17162
|
Object.assign(menuElement.style, {
|
|
16919
17163
|
position: 'fixed',
|
|
16920
|
-
left:
|
|
16921
|
-
top:
|
|
16922
|
-
transform: `translate(${Math.round(x)}px, ${Math.round(y)}px)`
|
|
17164
|
+
left: `${Math.round(x)}px`,
|
|
17165
|
+
top: `${Math.round(y)}px`,
|
|
16923
17166
|
});
|
|
16924
17167
|
}
|
|
16925
17168
|
async positionSubmenu(triggerElement, submenuElement) {
|
|
@@ -16948,9 +17191,8 @@ class ContextMenuService {
|
|
|
16948
17191
|
});
|
|
16949
17192
|
Object.assign(submenuElement.style, {
|
|
16950
17193
|
position: 'fixed',
|
|
16951
|
-
left:
|
|
16952
|
-
top:
|
|
16953
|
-
transform: `translate(${Math.round(x)}px, ${Math.round(y)}px)`
|
|
17194
|
+
left: `${Math.round(x)}px`,
|
|
17195
|
+
top: `${Math.round(y)}px`,
|
|
16954
17196
|
});
|
|
16955
17197
|
}
|
|
16956
17198
|
performAction(actionId) {
|
|
@@ -28177,6 +28419,12 @@ class ChartService {
|
|
|
28177
28419
|
margin = { top: 8, right: 8, bottom: 24, left: 40 };
|
|
28178
28420
|
/** Fraction of a band's step spent on padding, split either side. */
|
|
28179
28421
|
bandPadding = 0.2;
|
|
28422
|
+
/**
|
|
28423
|
+
* Which physical axis is the category axis. Bars flip this via `configure()`;
|
|
28424
|
+
* every other geometry getter branches on it internally, defaulting to the
|
|
28425
|
+
* exact vertical formula when unset, so existing vertical charts are unaffected.
|
|
28426
|
+
*/
|
|
28427
|
+
orientation = 'vertical';
|
|
28180
28428
|
scaleMode = 'point';
|
|
28181
28429
|
zeroRequired = false;
|
|
28182
28430
|
domainMin = 0;
|
|
@@ -28309,21 +28557,36 @@ class ChartService {
|
|
|
28309
28557
|
return total;
|
|
28310
28558
|
}
|
|
28311
28559
|
/**
|
|
28312
|
-
* Maps a data value to
|
|
28313
|
-
*
|
|
28560
|
+
* Maps a data value to a position along the *value* axis — svg y when
|
|
28561
|
+
* vertical (svg y grows downward, so the domain maximum lands on `plotTop`),
|
|
28562
|
+
* svg x when horizontal (the domain maximum lands on `plotRight`).
|
|
28314
28563
|
*/
|
|
28315
28564
|
yFor(value) {
|
|
28316
28565
|
const span = this.domainMax - this.domainMin;
|
|
28566
|
+
if (this.orientation === 'horizontal') {
|
|
28567
|
+
if (span <= 0)
|
|
28568
|
+
return this.plotLeft;
|
|
28569
|
+
const ratio = (value - this.domainMin) / span;
|
|
28570
|
+
return this.plotLeft + ratio * this.plotWidth;
|
|
28571
|
+
}
|
|
28317
28572
|
if (span <= 0)
|
|
28318
28573
|
return this.plotBottom;
|
|
28319
28574
|
const ratio = (value - this.domainMin) / span;
|
|
28320
28575
|
return this.plotBottom - ratio * this.plotHeight;
|
|
28321
28576
|
}
|
|
28322
|
-
/** The
|
|
28577
|
+
/** The value-axis position of the zero line, clamped into the plot. */
|
|
28323
28578
|
get baselineY() {
|
|
28324
28579
|
const zero = Math.min(Math.max(0, this.domainMin), this.domainMax);
|
|
28325
28580
|
return this.yFor(zero);
|
|
28326
28581
|
}
|
|
28582
|
+
/** Length of the plot along the category axis: `plotHeight` when horizontal, else `plotWidth`. */
|
|
28583
|
+
get categoryAxisLength() {
|
|
28584
|
+
return this.orientation === 'horizontal' ? this.plotHeight : this.plotWidth;
|
|
28585
|
+
}
|
|
28586
|
+
/** Origin of the plot along the category axis: `plotTop` when horizontal, else `plotLeft`. */
|
|
28587
|
+
get categoryAxisOrigin() {
|
|
28588
|
+
return this.orientation === 'horizontal' ? this.plotTop : this.plotLeft;
|
|
28589
|
+
}
|
|
28327
28590
|
/** Width of one band, excluding its padding. Meaningful in band mode. */
|
|
28328
28591
|
get bandWidth() {
|
|
28329
28592
|
if (this.count === 0)
|
|
@@ -28334,13 +28597,16 @@ class ChartService {
|
|
|
28334
28597
|
get bandStep() {
|
|
28335
28598
|
if (this.count === 0)
|
|
28336
28599
|
return 0;
|
|
28337
|
-
return this.
|
|
28600
|
+
return this.categoryAxisLength / this.count;
|
|
28338
28601
|
}
|
|
28339
|
-
/**
|
|
28602
|
+
/**
|
|
28603
|
+
* Geometry of the band at `index`, along the category axis — x when
|
|
28604
|
+
* vertical (today's meaning), y when horizontal.
|
|
28605
|
+
*/
|
|
28340
28606
|
bandFor(index) {
|
|
28341
28607
|
const step = this.bandStep;
|
|
28342
28608
|
const width = this.bandWidth;
|
|
28343
|
-
const start = this.
|
|
28609
|
+
const start = this.categoryAxisOrigin + index * step + (step - width) / 2;
|
|
28344
28610
|
return { start, centre: start + width / 2, width };
|
|
28345
28611
|
}
|
|
28346
28612
|
/** x of the data point at `index` on the point scale. */
|
|
@@ -28363,7 +28629,7 @@ class ChartService {
|
|
|
28363
28629
|
hitBandFor(index) {
|
|
28364
28630
|
if (this.scaleMode === 'band') {
|
|
28365
28631
|
const step = this.bandStep;
|
|
28366
|
-
const start = this.
|
|
28632
|
+
const start = this.categoryAxisOrigin + index * step;
|
|
28367
28633
|
return { start, centre: start + step / 2, width: step };
|
|
28368
28634
|
}
|
|
28369
28635
|
const x = this.pointX(index);
|
|
@@ -28437,6 +28703,7 @@ class ChartService {
|
|
|
28437
28703
|
this.height,
|
|
28438
28704
|
this.bandPadding,
|
|
28439
28705
|
this.scaleMode,
|
|
28706
|
+
this.orientation,
|
|
28440
28707
|
this.zeroRequired,
|
|
28441
28708
|
this.margin.top, this.margin.right, this.margin.bottom, this.margin.left,
|
|
28442
28709
|
this.domainMin, this.domainMax, this.tickStep,
|
|
@@ -29083,6 +29350,11 @@ class ChartComponent {
|
|
|
29083
29350
|
description = '';
|
|
29084
29351
|
/** Stacks marks on a shared baseline instead of grouping them. @default false */
|
|
29085
29352
|
stacked = false;
|
|
29353
|
+
/**
|
|
29354
|
+
* Which physical axis carries the category vs the value. Only bars support
|
|
29355
|
+
* 'horizontal' — line/area marks stay vertical regardless. @default 'vertical'
|
|
29356
|
+
*/
|
|
29357
|
+
orientation = 'vertical';
|
|
29086
29358
|
/** Renders the crosshair, hit layer and shared tooltip. @default true */
|
|
29087
29359
|
hover = true;
|
|
29088
29360
|
/** Shows the data table instead of leaving it visually hidden. @default false */
|
|
@@ -29155,6 +29427,7 @@ class ChartComponent {
|
|
|
29155
29427
|
series: this.series ?? [],
|
|
29156
29428
|
xKey: this.xKey,
|
|
29157
29429
|
stacked: this.stacked,
|
|
29430
|
+
orientation: this.orientation,
|
|
29158
29431
|
height: this.height,
|
|
29159
29432
|
margin: this.margin,
|
|
29160
29433
|
width: this.chart.width || ChartComponent.fallbackWidth,
|
|
@@ -29166,11 +29439,19 @@ class ChartComponent {
|
|
|
29166
29439
|
/**
|
|
29167
29440
|
* One catchment rect per x position, deliberately wider than the marks it
|
|
29168
29441
|
* covers — readers aim at a category, never at a 2px line or an 8px dot.
|
|
29442
|
+
* Spans the full plot in the cross direction: full height in a vertical
|
|
29443
|
+
* chart, full width in a horizontal one.
|
|
29169
29444
|
*/
|
|
29170
|
-
get
|
|
29445
|
+
get hitRects() {
|
|
29171
29446
|
if (!this.hover)
|
|
29172
29447
|
return [];
|
|
29173
|
-
|
|
29448
|
+
const horizontal = this.chart.orientation === 'horizontal';
|
|
29449
|
+
return this.data.map((_, i) => {
|
|
29450
|
+
const band = this.chart.hitBandFor(i);
|
|
29451
|
+
return horizontal
|
|
29452
|
+
? { x: this.chart.plotLeft, y: band.start, width: this.chart.plotWidth, height: band.width }
|
|
29453
|
+
: { x: band.start, y: this.chart.plotTop, width: band.width, height: this.chart.plotHeight };
|
|
29454
|
+
});
|
|
29174
29455
|
}
|
|
29175
29456
|
onEnterBand(index) {
|
|
29176
29457
|
this.chart.setActiveIndex(index);
|
|
@@ -29182,7 +29463,7 @@ class ChartComponent {
|
|
|
29182
29463
|
return cn(chartVariants({ variant: this.variant, density: this.density }), this.class);
|
|
29183
29464
|
}
|
|
29184
29465
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
29185
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ChartComponent, isStandalone: true, selector: "tolle-chart", inputs: { data: "data", series: "series", xKey: "xKey", height: "height", margin: "margin", ariaLabel: "ariaLabel", description: "description", stacked: "stacked", hover: "hover", showTable: "showTable", xHeader: "xHeader", variant: "variant", density: "density", class: "class" }, outputs: { activeIndexChange: "activeIndexChange" }, providers: [ChartService], viewQueries: [{ propertyName: "plotRef", first: true, predicate: ["plot"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
29466
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ChartComponent, isStandalone: true, selector: "tolle-chart", inputs: { data: "data", series: "series", xKey: "xKey", height: "height", margin: "margin", ariaLabel: "ariaLabel", description: "description", stacked: "stacked", orientation: "orientation", hover: "hover", showTable: "showTable", xHeader: "xHeader", variant: "variant", density: "density", class: "class" }, outputs: { activeIndexChange: "activeIndexChange" }, providers: [ChartService], viewQueries: [{ propertyName: "plotRef", first: true, predicate: ["plot"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
29186
29467
|
<div [class]="computedClass">
|
|
29187
29468
|
<div class="relative w-full" #plot>
|
|
29188
29469
|
<svg
|
|
@@ -29213,11 +29494,11 @@ class ChartComponent {
|
|
|
29213
29494
|
></svg:line>
|
|
29214
29495
|
|
|
29215
29496
|
<svg:rect
|
|
29216
|
-
*ngFor="let
|
|
29217
|
-
[attr.x]="
|
|
29218
|
-
[attr.y]="
|
|
29219
|
-
[attr.width]="
|
|
29220
|
-
[attr.height]="
|
|
29497
|
+
*ngFor="let rect of hitRects; let i = index; trackBy: trackByIndex"
|
|
29498
|
+
[attr.x]="rect.x"
|
|
29499
|
+
[attr.y]="rect.y"
|
|
29500
|
+
[attr.width]="rect.width"
|
|
29501
|
+
[attr.height]="rect.height"
|
|
29221
29502
|
fill="transparent"
|
|
29222
29503
|
(pointerenter)="onEnterBand(i)"
|
|
29223
29504
|
></svg:rect>
|
|
@@ -29265,11 +29546,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
29265
29546
|
></svg:line>
|
|
29266
29547
|
|
|
29267
29548
|
<svg:rect
|
|
29268
|
-
*ngFor="let
|
|
29269
|
-
[attr.x]="
|
|
29270
|
-
[attr.y]="
|
|
29271
|
-
[attr.width]="
|
|
29272
|
-
[attr.height]="
|
|
29549
|
+
*ngFor="let rect of hitRects; let i = index; trackBy: trackByIndex"
|
|
29550
|
+
[attr.x]="rect.x"
|
|
29551
|
+
[attr.y]="rect.y"
|
|
29552
|
+
[attr.width]="rect.width"
|
|
29553
|
+
[attr.height]="rect.height"
|
|
29273
29554
|
fill="transparent"
|
|
29274
29555
|
(pointerenter)="onEnterBand(i)"
|
|
29275
29556
|
></svg:rect>
|
|
@@ -29299,6 +29580,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
29299
29580
|
type: Input
|
|
29300
29581
|
}], stacked: [{
|
|
29301
29582
|
type: Input
|
|
29583
|
+
}], orientation: [{
|
|
29584
|
+
type: Input
|
|
29302
29585
|
}], hover: [{
|
|
29303
29586
|
type: Input
|
|
29304
29587
|
}], showTable: [{
|
|
@@ -29317,16 +29600,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
29317
29600
|
type: ViewChild,
|
|
29318
29601
|
args: ['plot']
|
|
29319
29602
|
}] } });
|
|
29320
|
-
/**
|
|
29603
|
+
/**
|
|
29604
|
+
* Rules at the value ticks, with optional rules at the category positions.
|
|
29605
|
+
* Orientation-aware: in a horizontal chart the value rules run vertically and
|
|
29606
|
+
* the (optional) category rules run horizontally — the transpose of a
|
|
29607
|
+
* vertical chart's grid.
|
|
29608
|
+
*/
|
|
29321
29609
|
class ChartGridComponent extends ChartChild {
|
|
29322
|
-
/** Also draws a rule at every
|
|
29610
|
+
/** Also draws a rule at every category position. @default false */
|
|
29323
29611
|
vertical = false;
|
|
29324
29612
|
/** Extra Tailwind classes merged onto each rule via `cn()` (last-wins). */
|
|
29325
29613
|
class = '';
|
|
29326
|
-
|
|
29614
|
+
/** Rules at each value tick, spanning the plot in the cross direction. */
|
|
29615
|
+
get valueLines() {
|
|
29616
|
+
const horizontal = this.chart.orientation === 'horizontal';
|
|
29617
|
+
return this.chart.ticks.map((tick) => horizontal
|
|
29618
|
+
? { x1: tick.y, x2: tick.y, y1: this.chart.plotTop, y2: this.chart.plotBottom }
|
|
29619
|
+
: { x1: this.chart.plotLeft, x2: this.chart.plotRight, y1: tick.y, y2: tick.y });
|
|
29620
|
+
}
|
|
29621
|
+
/** Rules at each category position, only when `vertical` is set. */
|
|
29622
|
+
get categoryLines() {
|
|
29327
29623
|
if (!this.vertical)
|
|
29328
29624
|
return [];
|
|
29329
|
-
|
|
29625
|
+
const horizontal = this.chart.orientation === 'horizontal';
|
|
29626
|
+
return this.chart.xLabels.map((_, i) => {
|
|
29627
|
+
const pos = this.chart.xFor(i);
|
|
29628
|
+
return horizontal
|
|
29629
|
+
? { x1: this.chart.plotLeft, x2: this.chart.plotRight, y1: pos, y2: pos }
|
|
29630
|
+
: { x1: pos, x2: pos, y1: this.chart.plotTop, y2: this.chart.plotBottom };
|
|
29631
|
+
});
|
|
29330
29632
|
}
|
|
29331
29633
|
get lineClass() {
|
|
29332
29634
|
// Recessive by design: a grid is a reading aid, never a mark.
|
|
@@ -29335,20 +29637,20 @@ class ChartGridComponent extends ChartChild {
|
|
|
29335
29637
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChartGridComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
29336
29638
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ChartGridComponent, isStandalone: true, selector: "svg:g[tolle-chart-grid]", inputs: { vertical: "vertical", class: "class" }, usesInheritance: true, ngImport: i0, template: `
|
|
29337
29639
|
<svg:line
|
|
29338
|
-
*ngFor="let
|
|
29339
|
-
[attr.x1]="
|
|
29340
|
-
[attr.x2]="
|
|
29341
|
-
[attr.y1]="
|
|
29342
|
-
[attr.y2]="
|
|
29640
|
+
*ngFor="let line of valueLines; trackBy: trackByIndex"
|
|
29641
|
+
[attr.x1]="line.x1"
|
|
29642
|
+
[attr.x2]="line.x2"
|
|
29643
|
+
[attr.y1]="line.y1"
|
|
29644
|
+
[attr.y2]="line.y2"
|
|
29343
29645
|
[class]="lineClass"
|
|
29344
29646
|
stroke-width="1"
|
|
29345
29647
|
></svg:line>
|
|
29346
29648
|
<svg:line
|
|
29347
|
-
*ngFor="let
|
|
29348
|
-
[attr.x1]="
|
|
29349
|
-
[attr.x2]="
|
|
29350
|
-
[attr.y1]="
|
|
29351
|
-
[attr.y2]="
|
|
29649
|
+
*ngFor="let line of categoryLines; trackBy: trackByIndex"
|
|
29650
|
+
[attr.x1]="line.x1"
|
|
29651
|
+
[attr.x2]="line.x2"
|
|
29652
|
+
[attr.y1]="line.y1"
|
|
29653
|
+
[attr.y2]="line.y2"
|
|
29352
29654
|
[class]="lineClass"
|
|
29353
29655
|
stroke-width="1"
|
|
29354
29656
|
></svg:line>
|
|
@@ -29363,20 +29665,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
29363
29665
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
29364
29666
|
template: `
|
|
29365
29667
|
<svg:line
|
|
29366
|
-
*ngFor="let
|
|
29367
|
-
[attr.x1]="
|
|
29368
|
-
[attr.x2]="
|
|
29369
|
-
[attr.y1]="
|
|
29370
|
-
[attr.y2]="
|
|
29668
|
+
*ngFor="let line of valueLines; trackBy: trackByIndex"
|
|
29669
|
+
[attr.x1]="line.x1"
|
|
29670
|
+
[attr.x2]="line.x2"
|
|
29671
|
+
[attr.y1]="line.y1"
|
|
29672
|
+
[attr.y2]="line.y2"
|
|
29371
29673
|
[class]="lineClass"
|
|
29372
29674
|
stroke-width="1"
|
|
29373
29675
|
></svg:line>
|
|
29374
29676
|
<svg:line
|
|
29375
|
-
*ngFor="let
|
|
29376
|
-
[attr.x1]="
|
|
29377
|
-
[attr.x2]="
|
|
29378
|
-
[attr.y1]="
|
|
29379
|
-
[attr.y2]="
|
|
29677
|
+
*ngFor="let line of categoryLines; trackBy: trackByIndex"
|
|
29678
|
+
[attr.x1]="line.x1"
|
|
29679
|
+
[attr.x2]="line.x2"
|
|
29680
|
+
[attr.y1]="line.y1"
|
|
29681
|
+
[attr.y2]="line.y2"
|
|
29380
29682
|
[class]="lineClass"
|
|
29381
29683
|
stroke-width="1"
|
|
29382
29684
|
></svg:line>
|
|
@@ -29389,7 +29691,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
29389
29691
|
}] } });
|
|
29390
29692
|
/** X-axis tick labels, thinned so they never collide. */
|
|
29391
29693
|
class ChartXAxisComponent extends ChartChild {
|
|
29392
|
-
/**
|
|
29694
|
+
/**
|
|
29695
|
+
* Approximate px per character, used to decide when category labels would
|
|
29696
|
+
* collide. Only consulted in the vertical orientation. @default 7
|
|
29697
|
+
*/
|
|
29393
29698
|
charWidth = 7;
|
|
29394
29699
|
/** Extra Tailwind classes merged onto each label via `cn()` (last-wins). */
|
|
29395
29700
|
class = '';
|
|
@@ -29408,7 +29713,15 @@ class ChartXAxisComponent extends ChartChild {
|
|
|
29408
29713
|
return 1;
|
|
29409
29714
|
return Math.max(1, Math.ceil(needed / perLabel));
|
|
29410
29715
|
}
|
|
29716
|
+
/**
|
|
29717
|
+
* Vertical orientation: category labels, thinned so they never collide
|
|
29718
|
+
* (today's behaviour). Horizontal orientation: this is the bottom axis'
|
|
29719
|
+
* physical position, which a horizontal chart uses for its value ticks.
|
|
29720
|
+
*/
|
|
29411
29721
|
get visibleTicks() {
|
|
29722
|
+
if (this.chart.orientation === 'horizontal') {
|
|
29723
|
+
return this.chart.ticks.map((tick) => ({ x: tick.y, label: tick.label }));
|
|
29724
|
+
}
|
|
29412
29725
|
const stride = this.stride;
|
|
29413
29726
|
return this.chart.xLabels
|
|
29414
29727
|
.map((label, i) => ({ label, x: this.chart.xFor(i), i }))
|
|
@@ -29465,13 +29778,24 @@ class ChartYAxisComponent extends ChartChild {
|
|
|
29465
29778
|
get labelX() {
|
|
29466
29779
|
return this.chart.plotLeft - this.offset;
|
|
29467
29780
|
}
|
|
29781
|
+
/**
|
|
29782
|
+
* Vertical orientation: value ticks (today's behaviour). Horizontal
|
|
29783
|
+
* orientation: this is the left axis' physical position, which a
|
|
29784
|
+
* horizontal chart uses for its category labels.
|
|
29785
|
+
*/
|
|
29786
|
+
get visibleTicks() {
|
|
29787
|
+
if (this.chart.orientation === 'horizontal') {
|
|
29788
|
+
return this.chart.xLabels.map((label, i) => ({ label, y: this.chart.xFor(i) }));
|
|
29789
|
+
}
|
|
29790
|
+
return this.chart.ticks;
|
|
29791
|
+
}
|
|
29468
29792
|
get labelClass() {
|
|
29469
29793
|
return cn('fill-muted-foreground text-xs tabular-nums', this.class);
|
|
29470
29794
|
}
|
|
29471
29795
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChartYAxisComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
29472
29796
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ChartYAxisComponent, isStandalone: true, selector: "svg:g[tolle-chart-y-axis]", inputs: { offset: "offset", class: "class" }, usesInheritance: true, ngImport: i0, template: `
|
|
29473
29797
|
<svg:text
|
|
29474
|
-
*ngFor="let tick of
|
|
29798
|
+
*ngFor="let tick of visibleTicks; trackBy: trackByIndex"
|
|
29475
29799
|
[attr.x]="labelX"
|
|
29476
29800
|
[attr.y]="tick.y"
|
|
29477
29801
|
text-anchor="end"
|
|
@@ -29489,7 +29813,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
29489
29813
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
29490
29814
|
template: `
|
|
29491
29815
|
<svg:text
|
|
29492
|
-
*ngFor="let tick of
|
|
29816
|
+
*ngFor="let tick of visibleTicks; trackBy: trackByIndex"
|
|
29493
29817
|
[attr.x]="labelX"
|
|
29494
29818
|
[attr.y]="tick.y"
|
|
29495
29819
|
text-anchor="end"
|
|
@@ -29683,6 +30007,24 @@ function barPath(x, width, yBase, yValue, radius) {
|
|
|
29683
30007
|
' Q ' + right + ' ' + yValue + ' ' + right + ' ' + corner +
|
|
29684
30008
|
' L ' + right + ' ' + yBase + ' Z');
|
|
29685
30009
|
}
|
|
30010
|
+
/**
|
|
30011
|
+
* `barPath()` transposed for horizontal bars: fixed vertical extent
|
|
30012
|
+
* `[y, y+height]`, variable horizontal extent `[xBase, xValue]`, rounded at
|
|
30013
|
+
* the `xValue` (data) end.
|
|
30014
|
+
*/
|
|
30015
|
+
function barPathHorizontal(y, height, xBase, xValue, radius) {
|
|
30016
|
+
const width = Math.abs(xBase - xValue);
|
|
30017
|
+
const r = Math.max(0, Math.min(radius, height / 2, width));
|
|
30018
|
+
const bottom = y + height;
|
|
30019
|
+
const sign = xValue >= xBase ? 1 : -1;
|
|
30020
|
+
const corner = xValue - sign * r;
|
|
30021
|
+
return ('M ' + xBase + ' ' + y +
|
|
30022
|
+
' L ' + corner + ' ' + y +
|
|
30023
|
+
' Q ' + xValue + ' ' + y + ' ' + xValue + ' ' + (y + r) +
|
|
30024
|
+
' L ' + xValue + ' ' + (bottom - r) +
|
|
30025
|
+
' Q ' + xValue + ' ' + bottom + ' ' + corner + ' ' + bottom +
|
|
30026
|
+
' L ' + xBase + ' ' + bottom + ' Z');
|
|
30027
|
+
}
|
|
29686
30028
|
/**
|
|
29687
30029
|
* A bar mark for one series. Grouped beside its siblings by default, stacked
|
|
29688
30030
|
* when the container's `stacked` is set. The data end carries a 4px radius, the
|
|
@@ -29715,12 +30057,19 @@ class ChartBarComponent extends ChartChild {
|
|
|
29715
30057
|
get color() {
|
|
29716
30058
|
return this.chart.colorFor(this.seriesKey);
|
|
29717
30059
|
}
|
|
29718
|
-
/**
|
|
30060
|
+
/**
|
|
30061
|
+
* Geometry for every bar this series contributes. `x`/`width` are the bar's
|
|
30062
|
+
* footprint on the category axis and `yBase`/`yValue` its extent on the
|
|
30063
|
+
* value axis — physical x/y when vertical (the original meaning), but
|
|
30064
|
+
* transposed (x holds a vertical footprint, yBase/yValue hold horizontal
|
|
30065
|
+
* positions) when the chart is horizontal, same as `ChartService.yFor()`.
|
|
30066
|
+
*/
|
|
29719
30067
|
get bars() {
|
|
29720
30068
|
const out = [];
|
|
29721
30069
|
const seriesCount = Math.max(1, this.chart.series.length);
|
|
29722
30070
|
const found = this.chart.series.findIndex((item) => item.key === this.seriesKey);
|
|
29723
30071
|
const slotIndex = found < 0 ? 0 : found;
|
|
30072
|
+
const horizontal = this.chart.orientation === 'horizontal';
|
|
29724
30073
|
for (let i = 0; i < this.chart.count; i++) {
|
|
29725
30074
|
const value = this.chart.valueAt(this.seriesKey, i);
|
|
29726
30075
|
if (value == null)
|
|
@@ -29741,12 +30090,16 @@ class ChartBarComponent extends ChartChild {
|
|
|
29741
30090
|
const yBase = this.chart.yFor(base);
|
|
29742
30091
|
let yValue = this.chart.yFor(base + value);
|
|
29743
30092
|
// The gap between stacked segments comes off the data end, so what
|
|
29744
|
-
// separates them is surface showing through rather than a stroke.
|
|
30093
|
+
// separates them is surface showing through rather than a stroke. Signed
|
|
30094
|
+
// from the resolved positions (not the raw value) so it holds under
|
|
30095
|
+
// both orientations, whose value axis runs in opposite directions.
|
|
29745
30096
|
if (this.chart.stacked && Math.abs(yBase - yValue) > this.gap) {
|
|
29746
|
-
yValue +=
|
|
30097
|
+
yValue += Math.sign(yBase - yValue) * this.gap;
|
|
29747
30098
|
}
|
|
29748
30099
|
out.push({
|
|
29749
|
-
d:
|
|
30100
|
+
d: horizontal
|
|
30101
|
+
? barPathHorizontal(x, width, yBase, yValue, this.radius)
|
|
30102
|
+
: barPath(x, width, yBase, yValue, this.radius),
|
|
29750
30103
|
active: this.activeIndex === i,
|
|
29751
30104
|
x,
|
|
29752
30105
|
width,
|
|
@@ -30172,6 +30525,414 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
30172
30525
|
type: Output
|
|
30173
30526
|
}] } });
|
|
30174
30527
|
|
|
30528
|
+
/**
|
|
30529
|
+
* A minimal inline chart: one series, no grid, no axes, no legend — sized to
|
|
30530
|
+
* sit next to a number in a table cell or a stat card. A thin composition
|
|
30531
|
+
* over `tolle-chart`; it draws nothing of its own and inherits `--chart-1`
|
|
30532
|
+
* the same way any other single-series chart in this library does.
|
|
30533
|
+
* @new
|
|
30534
|
+
*/
|
|
30535
|
+
class ChartSparkComponent {
|
|
30536
|
+
/**
|
|
30537
|
+
* Angular writes a bound `class` input through its styling path, which does
|
|
30538
|
+
* not mark an OnPush component dirty — without this hook the component keeps
|
|
30539
|
+
* rendering the class it was born with.
|
|
30540
|
+
*/
|
|
30541
|
+
ngOnChanges() {
|
|
30542
|
+
this.cdr.markForCheck();
|
|
30543
|
+
}
|
|
30544
|
+
/** Rows to plot, in x order. @default [] */
|
|
30545
|
+
data = [];
|
|
30546
|
+
/** Row key holding each row's numeric value. @default 'value' */
|
|
30547
|
+
valueKey = 'value';
|
|
30548
|
+
/** Row key holding each row's x position. Only matters once `hover` is on. @default '' */
|
|
30549
|
+
xKey = '';
|
|
30550
|
+
/** Which mark to draw. @default 'line' */
|
|
30551
|
+
type = 'line';
|
|
30552
|
+
/** Interpolation for line/area. @default 'linear' */
|
|
30553
|
+
curve = 'linear';
|
|
30554
|
+
/** Largest bar thickness in px, used when `type` is 'bar'. @default 6 */
|
|
30555
|
+
maxBarWidth = 6;
|
|
30556
|
+
/** Height in px. @default 32 */
|
|
30557
|
+
height = 32;
|
|
30558
|
+
/** Renders the crosshair, hit layer and tooltip. @default false */
|
|
30559
|
+
hover = false;
|
|
30560
|
+
/** Shows the data table instead of leaving it visually hidden. @default false */
|
|
30561
|
+
showTable = false;
|
|
30562
|
+
/** Accessible name, used as the svg <title>. @default '' */
|
|
30563
|
+
ariaLabel = '';
|
|
30564
|
+
/** Longer summary used as the svg <desc>. Falls back to `ariaLabel`. @default '' */
|
|
30565
|
+
description = '';
|
|
30566
|
+
/** Space reserved around the plot — a sparkline has no axis labels to fit. @default { top: 2, right: 2, bottom: 2, left: 2 } */
|
|
30567
|
+
margin = { top: 2, right: 2, bottom: 2, left: 2 };
|
|
30568
|
+
/** Extra Tailwind classes merged onto the chart via `cn()` (last-wins). */
|
|
30569
|
+
class = '';
|
|
30570
|
+
cdr = inject(ChangeDetectorRef);
|
|
30571
|
+
/** Always exactly one series, reading `valueKey` off each row. */
|
|
30572
|
+
get series() {
|
|
30573
|
+
return [{ key: this.valueKey, label: this.ariaLabel || this.valueKey }];
|
|
30574
|
+
}
|
|
30575
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChartSparkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
30576
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ChartSparkComponent, isStandalone: true, selector: "tolle-chart-spark", inputs: { data: "data", valueKey: "valueKey", xKey: "xKey", type: "type", curve: "curve", maxBarWidth: "maxBarWidth", height: "height", hover: "hover", showTable: "showTable", ariaLabel: "ariaLabel", description: "description", margin: "margin", class: "class" }, usesOnChanges: true, ngImport: i0, template: `
|
|
30577
|
+
<tolle-chart
|
|
30578
|
+
[class]="class"
|
|
30579
|
+
[data]="data"
|
|
30580
|
+
[series]="series"
|
|
30581
|
+
[xKey]="xKey"
|
|
30582
|
+
[height]="height"
|
|
30583
|
+
[margin]="margin"
|
|
30584
|
+
[hover]="hover"
|
|
30585
|
+
[showTable]="showTable"
|
|
30586
|
+
[ariaLabel]="ariaLabel"
|
|
30587
|
+
[description]="description"
|
|
30588
|
+
>
|
|
30589
|
+
<svg:g *ngIf="type === 'line'" tolle-chart-line [seriesKey]="valueKey" [curve]="curve"></svg:g>
|
|
30590
|
+
<svg:g *ngIf="type === 'area'" tolle-chart-area [seriesKey]="valueKey" [curve]="curve"></svg:g>
|
|
30591
|
+
<svg:g *ngIf="type === 'bar'" tolle-chart-bar [seriesKey]="valueKey" [maxWidth]="maxBarWidth"></svg:g>
|
|
30592
|
+
</tolle-chart>
|
|
30593
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ChartComponent, selector: "tolle-chart", inputs: ["data", "series", "xKey", "height", "margin", "ariaLabel", "description", "stacked", "orientation", "hover", "showTable", "xHeader", "variant", "density", "class"], outputs: ["activeIndexChange"] }, { kind: "component", type: ChartLineComponent, selector: "svg:g[tolle-chart-line]", inputs: ["seriesKey", "curve", "showDots", "dotRadius", "class"] }, { kind: "component", type: ChartAreaComponent, selector: "svg:g[tolle-chart-area]", inputs: ["seriesKey", "curve", "fillOpacity", "class"] }, { kind: "component", type: ChartBarComponent, selector: "svg:g[tolle-chart-bar]", inputs: ["seriesKey", "radius", "maxWidth", "gap", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
30594
|
+
}
|
|
30595
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChartSparkComponent, decorators: [{
|
|
30596
|
+
type: Component,
|
|
30597
|
+
args: [{ selector: 'tolle-chart-spark', standalone: true, imports: [CommonModule, ChartComponent, ChartLineComponent, ChartAreaComponent, ChartBarComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
30598
|
+
<tolle-chart
|
|
30599
|
+
[class]="class"
|
|
30600
|
+
[data]="data"
|
|
30601
|
+
[series]="series"
|
|
30602
|
+
[xKey]="xKey"
|
|
30603
|
+
[height]="height"
|
|
30604
|
+
[margin]="margin"
|
|
30605
|
+
[hover]="hover"
|
|
30606
|
+
[showTable]="showTable"
|
|
30607
|
+
[ariaLabel]="ariaLabel"
|
|
30608
|
+
[description]="description"
|
|
30609
|
+
>
|
|
30610
|
+
<svg:g *ngIf="type === 'line'" tolle-chart-line [seriesKey]="valueKey" [curve]="curve"></svg:g>
|
|
30611
|
+
<svg:g *ngIf="type === 'area'" tolle-chart-area [seriesKey]="valueKey" [curve]="curve"></svg:g>
|
|
30612
|
+
<svg:g *ngIf="type === 'bar'" tolle-chart-bar [seriesKey]="valueKey" [maxWidth]="maxBarWidth"></svg:g>
|
|
30613
|
+
</tolle-chart>
|
|
30614
|
+
`, styles: [":host{display:block}\n"] }]
|
|
30615
|
+
}], propDecorators: { data: [{
|
|
30616
|
+
type: Input
|
|
30617
|
+
}], valueKey: [{
|
|
30618
|
+
type: Input
|
|
30619
|
+
}], xKey: [{
|
|
30620
|
+
type: Input
|
|
30621
|
+
}], type: [{
|
|
30622
|
+
type: Input
|
|
30623
|
+
}], curve: [{
|
|
30624
|
+
type: Input
|
|
30625
|
+
}], maxBarWidth: [{
|
|
30626
|
+
type: Input
|
|
30627
|
+
}], height: [{
|
|
30628
|
+
type: Input
|
|
30629
|
+
}], hover: [{
|
|
30630
|
+
type: Input
|
|
30631
|
+
}], showTable: [{
|
|
30632
|
+
type: Input
|
|
30633
|
+
}], ariaLabel: [{
|
|
30634
|
+
type: Input
|
|
30635
|
+
}], description: [{
|
|
30636
|
+
type: Input
|
|
30637
|
+
}], margin: [{
|
|
30638
|
+
type: Input
|
|
30639
|
+
}], class: [{
|
|
30640
|
+
type: Input
|
|
30641
|
+
}] } });
|
|
30642
|
+
|
|
30643
|
+
/**
|
|
30644
|
+
* A single bar split into proportional, labelled ranges — for showing where
|
|
30645
|
+
* one value falls against a scale made of categories (a credit score inside
|
|
30646
|
+
* "Poor / Fair / Good / Excellent", latency inside "Fast / OK / Slow").
|
|
30647
|
+
*
|
|
30648
|
+
* Segments take the chart palette by position, since a category bar has no
|
|
30649
|
+
* stable per-render identity to key colour off the way a chart series does —
|
|
30650
|
+
* pass `colors` to override.
|
|
30651
|
+
* @new
|
|
30652
|
+
*/
|
|
30653
|
+
class CategoryBarComponent {
|
|
30654
|
+
/**
|
|
30655
|
+
* Angular writes a bound `class` input through its styling path, which does
|
|
30656
|
+
* not mark an OnPush component dirty — without this hook the component keeps
|
|
30657
|
+
* rendering the class it was born with.
|
|
30658
|
+
*/
|
|
30659
|
+
ngOnChanges() {
|
|
30660
|
+
this.cdr.markForCheck();
|
|
30661
|
+
}
|
|
30662
|
+
/** Identity for `*ngFor`, so a redraw patches nodes instead of replacing them. */
|
|
30663
|
+
trackByIndex = (index) => index;
|
|
30664
|
+
/** Width of each segment. Values that are not finite and greater than zero are dropped. @default [] */
|
|
30665
|
+
values = [];
|
|
30666
|
+
/** Name for each segment, used in the accessible summary and as a title tooltip. @default [] */
|
|
30667
|
+
labels = [];
|
|
30668
|
+
/** Paint for each segment, by index. Falls back to the chart palette --chart-1 through --chart-5. @default [] */
|
|
30669
|
+
colors = [];
|
|
30670
|
+
/** Position of the pointer, 0-100 on the same scale the segments sum to. Omit to draw no pointer. @default null */
|
|
30671
|
+
markerValue = null;
|
|
30672
|
+
/** Accessible name, prefixed onto the sr-only summary of segments and marker. @default '' */
|
|
30673
|
+
ariaLabel = '';
|
|
30674
|
+
/** Extra Tailwind classes merged onto the bar via `cn()` (last-wins). */
|
|
30675
|
+
class = '';
|
|
30676
|
+
cdr = inject(ChangeDetectorRef);
|
|
30677
|
+
get total() {
|
|
30678
|
+
return this.values.reduce((sum, v) => (Number.isFinite(v) && v > 0 ? sum + v : sum), 0);
|
|
30679
|
+
}
|
|
30680
|
+
get segments() {
|
|
30681
|
+
const total = this.total;
|
|
30682
|
+
if (total <= 0)
|
|
30683
|
+
return [];
|
|
30684
|
+
return this.values
|
|
30685
|
+
.map((value, i) => ({ value, i }))
|
|
30686
|
+
.filter(({ value }) => Number.isFinite(value) && value > 0)
|
|
30687
|
+
.map(({ value, i }) => ({
|
|
30688
|
+
label: this.labels[i] ?? '',
|
|
30689
|
+
value,
|
|
30690
|
+
percent: (value / total) * 100,
|
|
30691
|
+
color: this.colors[i] || 'rgb(var(--chart-' + ((i % 5) + 1) + '))',
|
|
30692
|
+
}));
|
|
30693
|
+
}
|
|
30694
|
+
get clampedMarker() {
|
|
30695
|
+
if (this.markerValue == null || !Number.isFinite(this.markerValue))
|
|
30696
|
+
return null;
|
|
30697
|
+
return Math.min(100, Math.max(0, this.markerValue));
|
|
30698
|
+
}
|
|
30699
|
+
get summary() {
|
|
30700
|
+
const parts = this.segments.map((s) => (s.label ? s.label + ': ' : '') + s.percent.toFixed(0) + '%');
|
|
30701
|
+
const marker = this.clampedMarker !== null ? ', current position ' + this.clampedMarker.toFixed(0) + '%' : '';
|
|
30702
|
+
const prefix = this.ariaLabel ? this.ariaLabel + '. ' : '';
|
|
30703
|
+
return prefix + parts.join(', ') + marker;
|
|
30704
|
+
}
|
|
30705
|
+
get computedClass() {
|
|
30706
|
+
return cn('w-full', this.class);
|
|
30707
|
+
}
|
|
30708
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CategoryBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
30709
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CategoryBarComponent, isStandalone: true, selector: "tolle-category-bar", inputs: { values: "values", labels: "labels", colors: "colors", markerValue: "markerValue", ariaLabel: "ariaLabel", class: "class" }, usesOnChanges: true, ngImport: i0, template: `
|
|
30710
|
+
<div [class]="computedClass">
|
|
30711
|
+
<div class="relative flex w-full gap-0.5">
|
|
30712
|
+
<div
|
|
30713
|
+
*ngFor="let segment of segments; let i = index; trackBy: trackByIndex"
|
|
30714
|
+
class="h-2 first:rounded-l-full last:rounded-r-full"
|
|
30715
|
+
[style.width.%]="segment.percent"
|
|
30716
|
+
[style.background]="segment.color"
|
|
30717
|
+
[attr.title]="segment.label || null"
|
|
30718
|
+
></div>
|
|
30719
|
+
|
|
30720
|
+
<span
|
|
30721
|
+
*ngIf="clampedMarker !== null"
|
|
30722
|
+
class="absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-background bg-foreground shadow"
|
|
30723
|
+
[style.left.%]="clampedMarker"
|
|
30724
|
+
></span>
|
|
30725
|
+
</div>
|
|
30726
|
+
|
|
30727
|
+
<p class="sr-only">{{ summary }}</p>
|
|
30728
|
+
</div>
|
|
30729
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
30730
|
+
}
|
|
30731
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CategoryBarComponent, decorators: [{
|
|
30732
|
+
type: Component,
|
|
30733
|
+
args: [{ selector: 'tolle-category-bar', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
30734
|
+
<div [class]="computedClass">
|
|
30735
|
+
<div class="relative flex w-full gap-0.5">
|
|
30736
|
+
<div
|
|
30737
|
+
*ngFor="let segment of segments; let i = index; trackBy: trackByIndex"
|
|
30738
|
+
class="h-2 first:rounded-l-full last:rounded-r-full"
|
|
30739
|
+
[style.width.%]="segment.percent"
|
|
30740
|
+
[style.background]="segment.color"
|
|
30741
|
+
[attr.title]="segment.label || null"
|
|
30742
|
+
></div>
|
|
30743
|
+
|
|
30744
|
+
<span
|
|
30745
|
+
*ngIf="clampedMarker !== null"
|
|
30746
|
+
class="absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-background bg-foreground shadow"
|
|
30747
|
+
[style.left.%]="clampedMarker"
|
|
30748
|
+
></span>
|
|
30749
|
+
</div>
|
|
30750
|
+
|
|
30751
|
+
<p class="sr-only">{{ summary }}</p>
|
|
30752
|
+
</div>
|
|
30753
|
+
`, styles: [":host{display:block}\n"] }]
|
|
30754
|
+
}], propDecorators: { values: [{
|
|
30755
|
+
type: Input
|
|
30756
|
+
}], labels: [{
|
|
30757
|
+
type: Input
|
|
30758
|
+
}], colors: [{
|
|
30759
|
+
type: Input
|
|
30760
|
+
}], markerValue: [{
|
|
30761
|
+
type: Input
|
|
30762
|
+
}], ariaLabel: [{
|
|
30763
|
+
type: Input
|
|
30764
|
+
}], class: [{
|
|
30765
|
+
type: Input
|
|
30766
|
+
}] } });
|
|
30767
|
+
|
|
30768
|
+
const STATUS_COLOR = {
|
|
30769
|
+
success: 'rgb(var(--success))',
|
|
30770
|
+
warning: 'rgb(var(--warning))',
|
|
30771
|
+
error: 'rgb(var(--destructive))',
|
|
30772
|
+
neutral: 'rgb(var(--muted))',
|
|
30773
|
+
};
|
|
30774
|
+
/**
|
|
30775
|
+
* A row of equal-width blocks, one per period — an uptime or activity strip.
|
|
30776
|
+
* Each block is a real `<button>` so its tooltip is reachable by keyboard and
|
|
30777
|
+
* has a screen-reader name of its own, not just a hover affordance.
|
|
30778
|
+
* @new
|
|
30779
|
+
*/
|
|
30780
|
+
class TrackerComponent {
|
|
30781
|
+
/**
|
|
30782
|
+
* Angular writes a bound `class` input through its styling path, which does
|
|
30783
|
+
* not mark an OnPush component dirty — without this hook the component keeps
|
|
30784
|
+
* rendering the class it was born with.
|
|
30785
|
+
*/
|
|
30786
|
+
ngOnChanges() {
|
|
30787
|
+
this.cdr.markForCheck();
|
|
30788
|
+
}
|
|
30789
|
+
/** Identity for `*ngFor`, so a redraw patches nodes instead of replacing them. */
|
|
30790
|
+
trackByIndex = (index) => index;
|
|
30791
|
+
/** One block per period, in chronological order. @default [] */
|
|
30792
|
+
data = [];
|
|
30793
|
+
/** Height of each block in px. @default 32 */
|
|
30794
|
+
blockHeight = 32;
|
|
30795
|
+
/** Accessible name for the whole strip. @default '' */
|
|
30796
|
+
ariaLabel = '';
|
|
30797
|
+
/** Extra Tailwind classes merged onto the strip via `cn()` (last-wins). */
|
|
30798
|
+
class = '';
|
|
30799
|
+
cdr = inject(ChangeDetectorRef);
|
|
30800
|
+
colorFor(block) {
|
|
30801
|
+
return block.color || STATUS_COLOR[block.status ?? 'neutral'];
|
|
30802
|
+
}
|
|
30803
|
+
get computedClass() {
|
|
30804
|
+
return cn('flex w-full items-stretch gap-0.5', this.class);
|
|
30805
|
+
}
|
|
30806
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TrackerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
30807
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TrackerComponent, isStandalone: true, selector: "tolle-tracker", inputs: { data: "data", blockHeight: "blockHeight", ariaLabel: "ariaLabel", class: "class" }, usesOnChanges: true, ngImport: i0, template: `
|
|
30808
|
+
<div [class]="computedClass" role="group" [attr.aria-label]="ariaLabel || null">
|
|
30809
|
+
<button
|
|
30810
|
+
*ngFor="let block of data; let i = index; trackBy: trackByIndex"
|
|
30811
|
+
type="button"
|
|
30812
|
+
class="flex-1 rounded-[2px] transition-opacity hover:opacity-80 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
30813
|
+
[style.height.px]="blockHeight"
|
|
30814
|
+
[style.background]="colorFor(block)"
|
|
30815
|
+
[tolleTooltip]="block.tooltip || ''"
|
|
30816
|
+
[attr.aria-label]="block.tooltip || 'Period ' + (i + 1)"
|
|
30817
|
+
></button>
|
|
30818
|
+
</div>
|
|
30819
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: TooltipDirective, selector: "[tolleTooltip]", inputs: ["tolleTooltip", "placement"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
30820
|
+
}
|
|
30821
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TrackerComponent, decorators: [{
|
|
30822
|
+
type: Component,
|
|
30823
|
+
args: [{ selector: 'tolle-tracker', standalone: true, imports: [CommonModule, TooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
30824
|
+
<div [class]="computedClass" role="group" [attr.aria-label]="ariaLabel || null">
|
|
30825
|
+
<button
|
|
30826
|
+
*ngFor="let block of data; let i = index; trackBy: trackByIndex"
|
|
30827
|
+
type="button"
|
|
30828
|
+
class="flex-1 rounded-[2px] transition-opacity hover:opacity-80 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
30829
|
+
[style.height.px]="blockHeight"
|
|
30830
|
+
[style.background]="colorFor(block)"
|
|
30831
|
+
[tolleTooltip]="block.tooltip || ''"
|
|
30832
|
+
[attr.aria-label]="block.tooltip || 'Period ' + (i + 1)"
|
|
30833
|
+
></button>
|
|
30834
|
+
</div>
|
|
30835
|
+
`, styles: [":host{display:block}\n"] }]
|
|
30836
|
+
}], propDecorators: { data: [{
|
|
30837
|
+
type: Input
|
|
30838
|
+
}], blockHeight: [{
|
|
30839
|
+
type: Input
|
|
30840
|
+
}], ariaLabel: [{
|
|
30841
|
+
type: Input
|
|
30842
|
+
}], class: [{
|
|
30843
|
+
type: Input
|
|
30844
|
+
}] } });
|
|
30845
|
+
|
|
30846
|
+
/**
|
|
30847
|
+
* A ranked list of horizontal bars — label left, value right, bar width
|
|
30848
|
+
* scaled to each row's share of the list's largest value. Unlike a chart
|
|
30849
|
+
* series or a category bar's segments, every bar is the same accent: rank is
|
|
30850
|
+
* carried by length, not by a categorical palette.
|
|
30851
|
+
* @new
|
|
30852
|
+
*/
|
|
30853
|
+
class BarListComponent {
|
|
30854
|
+
/**
|
|
30855
|
+
* Angular writes a bound `class` input through its styling path, which does
|
|
30856
|
+
* not mark an OnPush component dirty — without this hook the component keeps
|
|
30857
|
+
* rendering the class it was born with.
|
|
30858
|
+
*/
|
|
30859
|
+
ngOnChanges() {
|
|
30860
|
+
this.cdr.markForCheck();
|
|
30861
|
+
}
|
|
30862
|
+
/** Identity for `*ngFor`, so a redraw patches nodes instead of replacing them. */
|
|
30863
|
+
trackByIndex = (index) => index;
|
|
30864
|
+
/** Rows to rank, in the order they should be drawn. @default [] */
|
|
30865
|
+
data = [];
|
|
30866
|
+
/** Row key holding each row's label. @default 'label' */
|
|
30867
|
+
labelKey = 'label';
|
|
30868
|
+
/** Row key holding each row's numeric value. @default 'value' */
|
|
30869
|
+
valueKey = 'value';
|
|
30870
|
+
/** Extra Tailwind classes merged onto the list via `cn()` (last-wins). */
|
|
30871
|
+
class = '';
|
|
30872
|
+
cdr = inject(ChangeDetectorRef);
|
|
30873
|
+
get rows() {
|
|
30874
|
+
const rows = this.data ?? [];
|
|
30875
|
+
const values = rows.map((row) => {
|
|
30876
|
+
const num = Number(row?.[this.valueKey]);
|
|
30877
|
+
return Number.isFinite(num) ? num : 0;
|
|
30878
|
+
});
|
|
30879
|
+
const max = values.reduce((m, v) => Math.max(m, v), 0);
|
|
30880
|
+
return rows.map((row, i) => {
|
|
30881
|
+
const raw = row?.[this.labelKey];
|
|
30882
|
+
const value = values[i];
|
|
30883
|
+
return {
|
|
30884
|
+
label: raw == null ? '' : String(raw),
|
|
30885
|
+
value,
|
|
30886
|
+
percent: max > 0 ? Math.max(0, Math.min(100, (value / max) * 100)) : 0,
|
|
30887
|
+
formattedValue: value.toLocaleString('en-US'),
|
|
30888
|
+
};
|
|
30889
|
+
});
|
|
30890
|
+
}
|
|
30891
|
+
get computedClass() {
|
|
30892
|
+
return cn('flex w-full flex-col gap-1', this.class);
|
|
30893
|
+
}
|
|
30894
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BarListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
30895
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: BarListComponent, isStandalone: true, selector: "tolle-bar-list", inputs: { data: "data", labelKey: "labelKey", valueKey: "valueKey", class: "class" }, usesOnChanges: true, ngImport: i0, template: `
|
|
30896
|
+
<div [class]="computedClass">
|
|
30897
|
+
<div
|
|
30898
|
+
*ngFor="let row of rows; trackBy: trackByIndex"
|
|
30899
|
+
class="relative flex items-center overflow-hidden rounded-sm"
|
|
30900
|
+
>
|
|
30901
|
+
<div class="absolute inset-y-0 left-0 rounded-sm bg-primary/15" [style.width.%]="row.percent"></div>
|
|
30902
|
+
<span class="relative z-10 truncate px-2 py-1.5 text-sm text-foreground">{{ row.label }}</span>
|
|
30903
|
+
<span class="relative z-10 ml-auto shrink-0 px-2 py-1.5 text-sm font-medium tabular-nums text-foreground">
|
|
30904
|
+
{{ row.formattedValue }}
|
|
30905
|
+
</span>
|
|
30906
|
+
</div>
|
|
30907
|
+
</div>
|
|
30908
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
30909
|
+
}
|
|
30910
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BarListComponent, decorators: [{
|
|
30911
|
+
type: Component,
|
|
30912
|
+
args: [{ selector: 'tolle-bar-list', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
30913
|
+
<div [class]="computedClass">
|
|
30914
|
+
<div
|
|
30915
|
+
*ngFor="let row of rows; trackBy: trackByIndex"
|
|
30916
|
+
class="relative flex items-center overflow-hidden rounded-sm"
|
|
30917
|
+
>
|
|
30918
|
+
<div class="absolute inset-y-0 left-0 rounded-sm bg-primary/15" [style.width.%]="row.percent"></div>
|
|
30919
|
+
<span class="relative z-10 truncate px-2 py-1.5 text-sm text-foreground">{{ row.label }}</span>
|
|
30920
|
+
<span class="relative z-10 ml-auto shrink-0 px-2 py-1.5 text-sm font-medium tabular-nums text-foreground">
|
|
30921
|
+
{{ row.formattedValue }}
|
|
30922
|
+
</span>
|
|
30923
|
+
</div>
|
|
30924
|
+
</div>
|
|
30925
|
+
`, styles: [":host{display:block}\n"] }]
|
|
30926
|
+
}], propDecorators: { data: [{
|
|
30927
|
+
type: Input
|
|
30928
|
+
}], labelKey: [{
|
|
30929
|
+
type: Input
|
|
30930
|
+
}], valueKey: [{
|
|
30931
|
+
type: Input
|
|
30932
|
+
}], class: [{
|
|
30933
|
+
type: Input
|
|
30934
|
+
}] } });
|
|
30935
|
+
|
|
30175
30936
|
/**
|
|
30176
30937
|
* Publishes the application's current writing direction so components can adapt
|
|
30177
30938
|
* without reading the DOM.
|
|
@@ -31665,5 +32426,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
31665
32426
|
* Generated bundle index. Do not edit.
|
|
31666
32427
|
*/
|
|
31667
32428
|
|
|
31668
|
-
export { AccordionComponent, AccordionItemComponent, AlertComponent, AlertDialogActionComponent, AlertDialogCancelComponent, AlertDialogComponent, AlertDialogContentComponent, AlertDialogDescriptionComponent, AlertDialogDynamicComponent, AlertDialogFooterComponent, AlertDialogHeaderComponent, AlertDialogPortalComponent, AlertDialogRef, AlertDialogService, AlertDialogTitleComponent, AlertDialogTriggerComponent, AspectRatioComponent, AttachmentActionsComponent, AttachmentComponent, AttachmentGroupComponent, AvatarComponent, AvatarFallbackComponent, BASE_PALETTES, BadgeComponent, BreadcrumbComponent, BreadcrumbItemComponent, BreadcrumbLinkComponent, BreadcrumbSeparatorComponent, BubbleActionsComponent, BubbleComponent, BubbleReactionsComponent, ButtonComponent, ButtonGroupComponent, CHART_COLOR_LIMIT, CHART_OVERFLOW_COLOR, CalendarComponent, CardComponent, CardContentComponent, CardDescriptionComponent, CardFooterComponent, CardHeaderComponent, CardTitleComponent, CarouselComponent, CarouselContainerDirective, CarouselContentDirective, CarouselContext, CarouselItemDirective, CarouselNextDirective, CarouselPreviousDirective, ChainOfThoughtComponent, ChainOfThoughtContentComponent, ChainOfThoughtHeaderComponent, ChainOfThoughtSearchResultComponent, ChainOfThoughtSearchResultsComponent, ChainOfThoughtService, ChainOfThoughtStepComponent, ChartAreaComponent, ChartBarComponent, ChartChild, ChartColorScale, ChartComponent, ChartGridComponent, ChartLegendComponent, ChartLineComponent, ChartPieComponent, ChartService, ChartTableComponent, ChartTooltipComponent, ChartXAxisComponent, ChartYAxisComponent, CheckboxComponent, CheckpointComponent, CollapsibleComponent, CollapsibleContentComponent, CollapsibleTriggerComponent, ComboboxComponent, CommandComponent, CommandDialogComponent, CommandEmptyComponent, CommandGroupComponent, CommandInputComponent, CommandItemComponent, CommandListComponent, CommandSeparatorComponent, CommandService, CommandShortcutComponent, ConfirmationComponent, ContextComponent, ContextContentComponent, ContextMenuComponent, ContextMenuService, ContextMenuTriggerDirective, ContextService, ContextTriggerComponent, ConversationComponent, ConversationContentComponent, ConversationEmptyStateComponent, ConversationScrollButtonComponent, ConversationService, CountryCodesService, CountrySelectorComponent, DataTableComponent, DatePickerComponent, DateRangePickerComponent, DateTimePickerComponent, DirectionDirective, DirectionService, DropdownItemComponent, DropdownLabelComponent, DropdownMenuComponent, DropdownSeparatorComponent, DropdownTriggerDirective, EmptyStateComponent, FieldComponent, FieldContentComponent, FieldDescriptionComponent, FieldErrorComponent, FieldGroupComponent, FieldLabelComponent, FieldLegendComponent, FieldSeparatorComponent, FieldSetComponent, FieldTitleComponent, HoverCardComponent, HoverCardContentComponent, HoverCardTriggerComponent, InlineCitationCardComponent, InlineCitationComponent, InlineCitationQuoteComponent, InputComponent, InputGroupAddonComponent, InputGroupButtonComponent, InputGroupComponent, InputGroupInputComponent, InputGroupTextComponent, InputGroupTextareaComponent, ItemActionsComponent, ItemComponent, ItemContentComponent, ItemDescriptionComponent, ItemFooterComponent, ItemGroupComponent, ItemHeaderComponent, ItemMediaComponent, ItemTitleComponent, KbdComponent, KbdGroupComponent, LabelComponent, MarkerComponent, MarkerGroupComponent, MaskedInputComponent, MenubarComponent, MenubarContentComponent, MenubarItemComponent, MenubarLabelComponent, MenubarMenuComponent, MenubarSeparatorComponent, MenubarService, MenubarTriggerComponent, MessageAlignService, MessageAvatarComponent, MessageComponent, MessageContentComponent, MessageFooterComponent, MessageGroupComponent, MessageHeaderComponent, MessageScrollerButtonComponent, MessageScrollerComponent, MessageScrollerContentComponent, MessageScrollerItemDirective, MessageScrollerService, MessageScrollerViewportComponent,
|
|
32429
|
+
export { AccordionComponent, AccordionItemComponent, AlertComponent, AlertDialogActionComponent, AlertDialogCancelComponent, AlertDialogComponent, AlertDialogContentComponent, AlertDialogDescriptionComponent, AlertDialogDynamicComponent, AlertDialogFooterComponent, AlertDialogHeaderComponent, AlertDialogPortalComponent, AlertDialogRef, AlertDialogService, AlertDialogTitleComponent, AlertDialogTriggerComponent, AspectRatioComponent, AttachmentActionsComponent, AttachmentComponent, AttachmentGroupComponent, AvatarComponent, AvatarFallbackComponent, BASE_PALETTES, BadgeComponent, BarListComponent, BreadcrumbComponent, BreadcrumbItemComponent, BreadcrumbLinkComponent, BreadcrumbSeparatorComponent, BubbleActionsComponent, BubbleComponent, BubbleReactionsComponent, ButtonComponent, ButtonGroupComponent, CHART_COLOR_LIMIT, CHART_OVERFLOW_COLOR, CLOSE_FALLBACK_MS, CalendarComponent, CardComponent, CardContentComponent, CardDescriptionComponent, CardFooterComponent, CardHeaderComponent, CardTitleComponent, CarouselComponent, CarouselContainerDirective, CarouselContentDirective, CarouselContext, CarouselItemDirective, CarouselNextDirective, CarouselPreviousDirective, CategoryBarComponent, ChainOfThoughtComponent, ChainOfThoughtContentComponent, ChainOfThoughtHeaderComponent, ChainOfThoughtSearchResultComponent, ChainOfThoughtSearchResultsComponent, ChainOfThoughtService, ChainOfThoughtStepComponent, ChartAreaComponent, ChartBarComponent, ChartChild, ChartColorScale, ChartComponent, ChartGridComponent, ChartLegendComponent, ChartLineComponent, ChartPieComponent, ChartService, ChartSparkComponent, ChartTableComponent, ChartTooltipComponent, ChartXAxisComponent, ChartYAxisComponent, CheckboxComponent, CheckpointComponent, CollapsibleComponent, CollapsibleContentComponent, CollapsibleTriggerComponent, ComboboxComponent, CommandComponent, CommandDialogComponent, CommandEmptyComponent, CommandGroupComponent, CommandInputComponent, CommandItemComponent, CommandListComponent, CommandSeparatorComponent, CommandService, CommandShortcutComponent, ConfirmationComponent, ContextComponent, ContextContentComponent, ContextMenuComponent, ContextMenuService, ContextMenuTriggerDirective, ContextService, ContextTriggerComponent, ConversationComponent, ConversationContentComponent, ConversationEmptyStateComponent, ConversationScrollButtonComponent, ConversationService, CountryCodesService, CountrySelectorComponent, DataTableComponent, DatePickerComponent, DateRangePickerComponent, DateTimePickerComponent, DirectionDirective, DirectionService, DropdownItemComponent, DropdownLabelComponent, DropdownMenuComponent, DropdownSeparatorComponent, DropdownTriggerDirective, EmptyStateComponent, FieldComponent, FieldContentComponent, FieldDescriptionComponent, FieldErrorComponent, FieldGroupComponent, FieldLabelComponent, FieldLegendComponent, FieldSeparatorComponent, FieldSetComponent, FieldTitleComponent, HoverCardComponent, HoverCardContentComponent, HoverCardTriggerComponent, InlineCitationCardComponent, InlineCitationComponent, InlineCitationQuoteComponent, InputComponent, InputGroupAddonComponent, InputGroupButtonComponent, InputGroupComponent, InputGroupInputComponent, InputGroupTextComponent, InputGroupTextareaComponent, ItemActionsComponent, ItemComponent, ItemContentComponent, ItemDescriptionComponent, ItemFooterComponent, ItemGroupComponent, ItemHeaderComponent, ItemMediaComponent, ItemTitleComponent, KbdComponent, KbdGroupComponent, LabelComponent, MarkerComponent, MarkerGroupComponent, MaskedInputComponent, MenubarComponent, MenubarContentComponent, MenubarItemComponent, MenubarLabelComponent, MenubarMenuComponent, MenubarSeparatorComponent, MenubarService, MenubarTriggerComponent, MessageAlignService, MessageAvatarComponent, MessageComponent, MessageContentComponent, MessageFooterComponent, MessageGroupComponent, MessageHeaderComponent, MessageScrollerButtonComponent, MessageScrollerComponent, MessageScrollerContentComponent, MessageScrollerItemDirective, MessageScrollerService, MessageScrollerViewportComponent, ModalComponent, ModalRef, ModalService, ModalStackService, ModelSelectorComponent, MultiSelectComponent, NativeSelectComponent, NavigationMenuComponent, NavigationMenuContentComponent, NavigationMenuItemComponent, NavigationMenuLinkComponent, NavigationMenuListComponent, NavigationMenuService, NavigationMenuTriggerComponent, OtpComponent, OtpGroupComponent, OtpSlotComponent, PaginationComponent, PhoneNumberInputComponent, PhoneNumberService, PlanComponent, PlanService, PlanStepComponent, PopoverComponent, PopoverContentComponent, ProgressCircleComponent, ProgressComponent, PromptInputComponent, PromptInputService, PromptInputSubmitComponent, PromptInputToolbarComponent, PromptInputToolsComponent, QueueComponent, QueueItemComponent, QueueService, RadioGroupComponent, RadioItemComponent, RadioService, RangeCalendarComponent, ReasoningComponent, ReasoningContentComponent, ReasoningService, ReasoningTriggerComponent, ResizableComponent, ResizablePanelComponent, ResizablePanelItemComponent, ScrollAreaComponent, SegmentComponent, SegmentComponent as SegmentedComponent, SelectComponent, SelectGroupComponent, SelectItemComponent, SelectSeparatorComponent, SelectService, SeparatorComponent, SheetComponent, SheetContentComponent, SheetDescriptionComponent, SheetFooterComponent, SheetHeaderComponent, SheetRef, SheetService, SheetTitleComponent, SheetTriggerComponent, SheetWrapperComponent, ShimmerComponent, SidebarComponent, SkeletonComponent, SliderComponent, SourceComponent, SourcesComponent, SourcesContentComponent, SourcesService, SourcesTriggerComponent, SpinnerComponent, SuggestionComponent, SuggestionsComponent, SuggestionsService, SwitchComponent, TOLLE_CONFIG, TableBodyDirective, TableCaptionDirective, TableCellDirective, TableComponent, TableFooterDirective, TableHeadDirective, TableHeaderDirective, TableRowDirective, TabsComponent, TabsContentComponent, TabsListComponent, TabsTriggerComponent, TagInputComponent, TaskComponent, TaskContentComponent, TaskItemComponent, TaskItemFileComponent, TaskService, TaskTriggerComponent, TextareaComponent, ThemeService, TimeColumnsComponent, TimePickerComponent, ToastContainerComponent, ToastService, ToggleComponent, ToggleGroupComponent, ToggleGroupItemComponent, TolleCellDirective, ToolComponent, ToolHeaderComponent, ToolInputComponent, ToolOutputComponent, ToolPayloadBase, ToolService, TooltipDirective, TrackerComponent, TypographyComponent, arcPath, barPath, barPathHorizontal, buildPath, cn, contrastTriplet, darkenColor, decimalsFor, formatTimeString, formatTokens, from12Hour, generateBaseCss, generateChartCss, generateChartRamp, generateThemeCss, getContrastColor, hexToHsl, hexToRgb, hslToHex, lightenColor, monotoneTangents, niceScale, niceStep, parseTimeString, polarPoint, provideTolleConfig, radiusScale, rgbTriplet, timePartsToSeconds, to12Hour };
|
|
31669
32430
|
//# sourceMappingURL=tolle-ui.mjs.map
|