@theseam/ui-common 1.0.2-beta.48 → 1.0.2-beta.54

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.
@@ -1,9 +1,44 @@
1
+ import { ComponentHarness } from '@angular/cdk/testing';
1
2
  import { __decorate } from 'tslib';
2
3
  import * as i0 from '@angular/core';
3
- import { Input, ViewEncapsulation, Component, NgModule } from '@angular/core';
4
+ import { Input, ViewEncapsulation, Component, inject, DestroyRef, input, signal, computed, effect, ChangeDetectionStrategy, booleanAttribute, output, NgModule } from '@angular/core';
4
5
  import { InputBoolean, InputNumber } from '@theseam/ui-common/core';
5
6
  import * as i1 from '@angular/common';
6
7
  import { CommonModule } from '@angular/common';
8
+ import { TheSeamTooltipDirective } from '@theseam/ui-common/tooltip';
9
+
10
+ /** Harness for a single cell inside `TheSeamSegmentedProgressBarComponent`. */
11
+ class TheSeamSegmentedProgressBarCellHarness extends ComponentHarness {
12
+ static hostSelector = 'seam-segmented-progress-bar-cell';
13
+ /** The visual state of the cell, derived from its host classes. */
14
+ async getState() {
15
+ const host = await this.host();
16
+ if (await host.hasClass('bg-success')) {
17
+ return 'complete';
18
+ }
19
+ return 'default';
20
+ }
21
+ async click() {
22
+ return (await this.host()).click();
23
+ }
24
+ }
25
+ /** Harness for `TheSeamSegmentedProgressBarComponent`. */
26
+ class TheSeamSegmentedProgressBarHarness extends ComponentHarness {
27
+ static hostSelector = 'seam-segmented-progress-bar';
28
+ _cells = this.locatorForAll(TheSeamSegmentedProgressBarCellHarness);
29
+ /** Gets harnesses for every rendered cell, in order. */
30
+ async getCells() {
31
+ return this._cells();
32
+ }
33
+ /** Clicks the cell at the given zero-based index. */
34
+ async clickCell(index) {
35
+ const cells = await this.getCells();
36
+ if (index < 0 || index >= cells.length) {
37
+ throw new Error(`TheSeamSegmentedProgressBarHarness.clickCell: index ${index} out of range (0..${cells.length - 1})`);
38
+ }
39
+ await cells[index].click();
40
+ }
41
+ }
7
42
 
8
43
  function calcDashoffset(value, circumference) {
9
44
  const progress = value / 100;
@@ -71,17 +106,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
71
106
  type: Input
72
107
  }] } });
73
108
 
109
+ class SegmentedProgressBarCellComponent {
110
+ _destroyRef = inject(DestroyRef);
111
+ step = input.required(...(ngDevMode ? [{ debugName: "step" }] : []));
112
+ _controlStatus = signal(null, ...(ngDevMode ? [{ debugName: "_controlStatus" }] : []));
113
+ _state = computed(() => {
114
+ const s = this.step();
115
+ if (s.completed !== undefined) {
116
+ return s.completed ? 'COMPLETE' : 'DEFAULT';
117
+ }
118
+ if (s.control) {
119
+ const status = this._controlStatus();
120
+ if (status === 'VALID' && (s.isCurrent || s.hasVisited)) {
121
+ return 'COMPLETE';
122
+ }
123
+ return 'DEFAULT';
124
+ }
125
+ return 'DEFAULT';
126
+ }, ...(ngDevMode ? [{ debugName: "_state" }] : []));
127
+ constructor() {
128
+ let sub;
129
+ effect(() => {
130
+ const s = this.step();
131
+ sub?.unsubscribe();
132
+ sub = undefined;
133
+ if (s.control) {
134
+ this._controlStatus.set(s.control.status);
135
+ sub = s.control.statusChanges.subscribe((status) => this._controlStatus.set(status));
136
+ }
137
+ else {
138
+ this._controlStatus.set(null);
139
+ }
140
+ });
141
+ this._destroyRef.onDestroy(() => sub?.unsubscribe());
142
+ }
143
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SegmentedProgressBarCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
144
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.15", type: SegmentedProgressBarCellComponent, isStandalone: true, selector: "seam-segmented-progress-bar-cell", inputs: { step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.bg-light": "_state() === \"DEFAULT\"", "class.bg-success": "_state() === \"COMPLETE\"" } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
145
+ }
146
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SegmentedProgressBarCellComponent, decorators: [{
147
+ type: Component,
148
+ args: [{
149
+ selector: 'seam-segmented-progress-bar-cell',
150
+ template: '',
151
+ host: {
152
+ '[class.bg-light]': '_state() === "DEFAULT"',
153
+ '[class.bg-success]': '_state() === "COMPLETE"',
154
+ },
155
+ changeDetection: ChangeDetectionStrategy.OnPush,
156
+ }]
157
+ }], ctorParameters: () => [], propDecorators: { step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: true }] }] } });
158
+
159
+ class TheSeamSegmentedProgressBarComponent {
160
+ progressSteps = input([], ...(ngDevMode ? [{ debugName: "progressSteps" }] : []));
161
+ clickable = input(false, ...(ngDevMode ? [{ debugName: "clickable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
162
+ enableTooltip = input(false, ...(ngDevMode ? [{ debugName: "enableTooltip", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
163
+ cellClicked = output();
164
+ onClickProgressCell(step) {
165
+ if (!this.clickable()) {
166
+ return;
167
+ }
168
+ this.cellClicked.emit(step);
169
+ }
170
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamSegmentedProgressBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
171
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: TheSeamSegmentedProgressBarComponent, isStandalone: true, selector: "seam-segmented-progress-bar", inputs: { progressSteps: { classPropertyName: "progressSteps", publicName: "progressSteps", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null }, enableTooltip: { classPropertyName: "enableTooltip", publicName: "enableTooltip", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cellClicked: "cellClicked" }, ngImport: i0, template: "<div class=\"progress-bar--container\">\n @for (step of progressSteps(); track step.value) {\n <seam-segmented-progress-bar-cell\n [step]=\"step\"\n class=\"progress-bar--cell border\"\n [class.is-clickable]=\"clickable()\"\n (click)=\"onClickProgressCell(step)\"\n [seamTooltip]=\"step.label\"\n placement=\"bottom\"\n container=\"body\"\n [disableTooltip]=\"!enableTooltip()\"\n />\n }\n</div>\n", styles: [".progress-bar--container{display:flex;flex-direction:row;flex-wrap:wrap}.progress-bar--container .progress-bar--cell{width:25px;height:20px;border-radius:3px}.progress-bar--container .progress-bar--cell.is-clickable:hover:not(:disabled){cursor:pointer}\n"], dependencies: [{ kind: "component", type: SegmentedProgressBarCellComponent, selector: "seam-segmented-progress-bar-cell", inputs: ["step"] }, { kind: "directive", type: TheSeamTooltipDirective, selector: "[seamTooltip]", inputs: ["seamTooltip", "tooltipClass", "placement", "container", "disableTooltip", "showDelay", "hideDelay", "trigger"], exportAs: ["seamTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
172
+ }
173
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamSegmentedProgressBarComponent, decorators: [{
174
+ type: Component,
175
+ args: [{ selector: 'seam-segmented-progress-bar', imports: [SegmentedProgressBarCellComponent, TheSeamTooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"progress-bar--container\">\n @for (step of progressSteps(); track step.value) {\n <seam-segmented-progress-bar-cell\n [step]=\"step\"\n class=\"progress-bar--cell border\"\n [class.is-clickable]=\"clickable()\"\n (click)=\"onClickProgressCell(step)\"\n [seamTooltip]=\"step.label\"\n placement=\"bottom\"\n container=\"body\"\n [disableTooltip]=\"!enableTooltip()\"\n />\n }\n</div>\n", styles: [".progress-bar--container{display:flex;flex-direction:row;flex-wrap:wrap}.progress-bar--container .progress-bar--cell{width:25px;height:20px;border-radius:3px}.progress-bar--container .progress-bar--cell.is-clickable:hover:not(:disabled){cursor:pointer}\n"] }]
176
+ }], propDecorators: { progressSteps: [{ type: i0.Input, args: [{ isSignal: true, alias: "progressSteps", required: false }] }], clickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickable", required: false }] }], enableTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableTooltip", required: false }] }], cellClicked: [{ type: i0.Output, args: ["cellClicked"] }] } });
177
+
74
178
  class TheSeamProgressModule {
75
179
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamProgressModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
76
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: TheSeamProgressModule, declarations: [ProgressCircleComponent], imports: [CommonModule], exports: [ProgressCircleComponent] });
180
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: TheSeamProgressModule, declarations: [ProgressCircleComponent], imports: [CommonModule, TheSeamSegmentedProgressBarComponent], exports: [ProgressCircleComponent, TheSeamSegmentedProgressBarComponent] });
77
181
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamProgressModule, imports: [CommonModule] });
78
182
  }
79
183
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamProgressModule, decorators: [{
80
184
  type: NgModule,
81
185
  args: [{
82
186
  declarations: [ProgressCircleComponent],
83
- imports: [CommonModule],
84
- exports: [ProgressCircleComponent],
187
+ imports: [CommonModule, TheSeamSegmentedProgressBarComponent],
188
+ exports: [ProgressCircleComponent, TheSeamSegmentedProgressBarComponent],
85
189
  }]
86
190
  }] });
87
191
 
@@ -89,5 +193,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
89
193
  * Generated bundle index. Do not edit.
90
194
  */
91
195
 
92
- export { ProgressCircleComponent, TheSeamProgressModule, calcDashoffset };
196
+ export { ProgressCircleComponent, TheSeamProgressModule, TheSeamSegmentedProgressBarCellHarness, TheSeamSegmentedProgressBarComponent, TheSeamSegmentedProgressBarHarness, calcDashoffset };
93
197
  //# sourceMappingURL=theseam-ui-common-progress.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"theseam-ui-common-progress.mjs","sources":["../../../projects/ui-common/progress/progress-circle/progress-circle.component.ts","../../../projects/ui-common/progress/progress-circle/progress-circle.component.html","../../../projects/ui-common/progress/progress.module.ts","../../../projects/ui-common/progress/theseam-ui-common-progress.ts"],"sourcesContent":["import { BooleanInput, NumberInput } from '@angular/cdk/coercion'\nimport { Component, Input, ViewEncapsulation } from '@angular/core'\n\nimport { InputBoolean, InputNumber } from '@theseam/ui-common/core'\n\ninterface IProgressInfo {\n dashoffset: number\n circumference: number\n percent: number\n}\n\nexport function calcDashoffset(value: number, circumference: number) {\n const progress = value / 100\n const dashoffset = circumference * (1 - progress)\n return dashoffset\n}\n\n@Component({\n selector: 'seam-progress-circle',\n templateUrl: './progress-circle.component.html',\n styleUrls: ['./progress-circle.component.scss'],\n encapsulation: ViewEncapsulation.None,\n standalone: false,\n})\nexport class ProgressCircleComponent {\n static ngAcceptInputType_fillBackground: BooleanInput\n static ngAcceptInputType_showText: BooleanInput\n static ngAcceptInputType_hiddenOnEmpty: BooleanInput\n static ngAcceptInputType_percentage: NumberInput\n static ngAcceptInputType_pending: BooleanInput\n\n private _percentage = 0\n\n @Input() @InputBoolean() fillBackground = false\n @Input() @InputBoolean() showText = false\n @Input() @InputBoolean() hiddenOnEmpty = true\n @Input() @InputBoolean() pending = false\n\n @Input()\n @InputNumber()\n set percentage(value: number) {\n this._percentage = value\n this._progressInfo = this._getProgress()\n }\n get percentage(): number {\n return this._percentage\n }\n\n public readonly radius = 15\n public readonly circumference = 2 * Math.PI * this.radius\n\n _progressInfo?: IProgressInfo | null\n\n private _getProgress(): IProgressInfo {\n return {\n dashoffset: calcDashoffset(this.percentage || 0, this.circumference),\n circumference: this.circumference,\n percent: Math.floor(this.percentage || 0),\n }\n }\n}\n","<div\n class=\"seam-progress-circle\"\n *ngIf=\"_progressInfo as p\"\n [class.seam-progress-circle--in-complete]=\"p.percent < 100\"\n [class.seam-progress-circle--fill-bg]=\"fillBackground\"\n>\n <div class=\"seam-progress-circle-inner\">\n <ng-container *ngIf=\"p.percent > 0 || !hiddenOnEmpty\">\n <svg\n class=\"seam-progress-circle--icon-spinner\"\n viewBox=\"0 0 40 40\"\n [attr.data-percent]=\"p.percent\"\n >\n <circle\n class=\"seam-progress-circle--icon-spinner-path seam-progress-circle--icon-spinner-path-bg\"\n cx=\"20\"\n cy=\"20\"\n r=\"15\"\n fill=\"none\"\n stroke-width=\"4\"\n ></circle>\n <circle\n class=\"seam-progress-circle--icon-spinner-path\"\n cx=\"20\"\n cy=\"20\"\n r=\"15\"\n fill=\"none\"\n transform=\"rotate(-90, 20, 20)\"\n stroke-width=\"4\"\n stroke-miterlimit=\"10\"\n [attr.stroke-dashoffset]=\"p.dashoffset\"\n [style.stroke-dasharray]=\"p.circumference\"\n ></circle>\n <!-- Checkmark -->\n <path\n *ngIf=\"!pending && p.percent === 100\"\n class=\"seam-progress-circle--icon-spinner-path\"\n fill=\"none\"\n stroke-width=\"4\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M 11,22 L 18,28 L 28.5,17\"\n ></path>\n <!-- Hourglass -->\n <!-- `p.percent === 101` is still here for backwards compatability, but planing to be removed. -->\n <path\n *ngIf=\"pending || p.percent === 101\"\n class=\"seam-progress-circle--icon-hourglass\"\n fill=\"none\"\n d=\"M27.3,25.8c-0.2-2.5-1.6-4.6-3.6-5.8c2-1.2,3.4-3.3,3.6-5.8c0.7-0.3,1.2-1,1.2-1.8c0-1.1-0.9-2-2-2h-13\n c-1.1,0-2,0.9-2,2c0,0.8,0.5,1.6,1.2,1.8c0.2,2.5,1.6,4.6,3.6,5.8c-2,1.2-3.4,3.3-3.6,5.8c-0.7,0.3-1.2,1-1.2,1.8c0,1.1,0.9,2,2,2\n h13c1.1,0,2-0.9,2-2C28.6,26.9,28,26.1,27.3,25.8z M24.3,25.7h-8.5c0.3-2.1,2.1-3.6,4.2-3.6S24,23.6,24.3,25.7z M20,18\n c-2.1,0-3.9-1.6-4.2-3.6h8.5C24,16.5,22.2,18,20,18z\"\n ></path>\n </svg>\n </ng-container>\n <div\n class=\"seam-progress-circle--icon-text\"\n *ngIf=\"showText && !pending && p.percent > 0 && p.percent < 100\"\n >\n <span>{{ p.percent }}</span>\n </div>\n </div>\n</div>\n","import { CommonModule } from '@angular/common'\nimport { NgModule } from '@angular/core'\n\nimport { ProgressCircleComponent } from './progress-circle/progress-circle.component'\n\n@NgModule({\n declarations: [ProgressCircleComponent],\n imports: [CommonModule],\n exports: [ProgressCircleComponent],\n})\nexport class TheSeamProgressModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"theseam-ui-common-progress.mjs","sources":["../../../projects/ui-common/progress/testing/segmented-progress-bar.harness.ts","../../../projects/ui-common/progress/progress-circle/progress-circle.component.ts","../../../projects/ui-common/progress/progress-circle/progress-circle.component.html","../../../projects/ui-common/progress/segmented-progress-bar/segmented-progress-bar-cell.component.ts","../../../projects/ui-common/progress/segmented-progress-bar/segmented-progress-bar.component.ts","../../../projects/ui-common/progress/segmented-progress-bar/segmented-progress-bar.component.html","../../../projects/ui-common/progress/progress.module.ts","../../../projects/ui-common/progress/theseam-ui-common-progress.ts"],"sourcesContent":["import { ComponentHarness } from '@angular/cdk/testing'\n\n/** Harness for a single cell inside `TheSeamSegmentedProgressBarComponent`. */\nexport class TheSeamSegmentedProgressBarCellHarness extends ComponentHarness {\n static hostSelector = 'seam-segmented-progress-bar-cell'\n\n /** The visual state of the cell, derived from its host classes. */\n async getState(): Promise<'default' | 'complete'> {\n const host = await this.host()\n if (await host.hasClass('bg-success')) {\n return 'complete'\n }\n return 'default'\n }\n\n async click(): Promise<void> {\n return (await this.host()).click()\n }\n}\n\n/** Harness for `TheSeamSegmentedProgressBarComponent`. */\nexport class TheSeamSegmentedProgressBarHarness extends ComponentHarness {\n static hostSelector = 'seam-segmented-progress-bar'\n\n private readonly _cells = this.locatorForAll(\n TheSeamSegmentedProgressBarCellHarness,\n )\n\n /** Gets harnesses for every rendered cell, in order. */\n async getCells(): Promise<TheSeamSegmentedProgressBarCellHarness[]> {\n return this._cells()\n }\n\n /** Clicks the cell at the given zero-based index. */\n async clickCell(index: number): Promise<void> {\n const cells = await this.getCells()\n if (index < 0 || index >= cells.length) {\n throw new Error(\n `TheSeamSegmentedProgressBarHarness.clickCell: index ${index} out of range (0..${cells.length - 1})`,\n )\n }\n await cells[index].click()\n }\n}\n","import { BooleanInput, NumberInput } from '@angular/cdk/coercion'\nimport { Component, Input, ViewEncapsulation } from '@angular/core'\n\nimport { InputBoolean, InputNumber } from '@theseam/ui-common/core'\n\ninterface IProgressInfo {\n dashoffset: number\n circumference: number\n percent: number\n}\n\nexport function calcDashoffset(value: number, circumference: number) {\n const progress = value / 100\n const dashoffset = circumference * (1 - progress)\n return dashoffset\n}\n\n@Component({\n selector: 'seam-progress-circle',\n templateUrl: './progress-circle.component.html',\n styleUrls: ['./progress-circle.component.scss'],\n encapsulation: ViewEncapsulation.None,\n standalone: false,\n})\nexport class ProgressCircleComponent {\n static ngAcceptInputType_fillBackground: BooleanInput\n static ngAcceptInputType_showText: BooleanInput\n static ngAcceptInputType_hiddenOnEmpty: BooleanInput\n static ngAcceptInputType_percentage: NumberInput\n static ngAcceptInputType_pending: BooleanInput\n\n private _percentage = 0\n\n @Input() @InputBoolean() fillBackground = false\n @Input() @InputBoolean() showText = false\n @Input() @InputBoolean() hiddenOnEmpty = true\n @Input() @InputBoolean() pending = false\n\n @Input()\n @InputNumber()\n set percentage(value: number) {\n this._percentage = value\n this._progressInfo = this._getProgress()\n }\n get percentage(): number {\n return this._percentage\n }\n\n public readonly radius = 15\n public readonly circumference = 2 * Math.PI * this.radius\n\n _progressInfo?: IProgressInfo | null\n\n private _getProgress(): IProgressInfo {\n return {\n dashoffset: calcDashoffset(this.percentage || 0, this.circumference),\n circumference: this.circumference,\n percent: Math.floor(this.percentage || 0),\n }\n }\n}\n","<div\n class=\"seam-progress-circle\"\n *ngIf=\"_progressInfo as p\"\n [class.seam-progress-circle--in-complete]=\"p.percent < 100\"\n [class.seam-progress-circle--fill-bg]=\"fillBackground\"\n>\n <div class=\"seam-progress-circle-inner\">\n <ng-container *ngIf=\"p.percent > 0 || !hiddenOnEmpty\">\n <svg\n class=\"seam-progress-circle--icon-spinner\"\n viewBox=\"0 0 40 40\"\n [attr.data-percent]=\"p.percent\"\n >\n <circle\n class=\"seam-progress-circle--icon-spinner-path seam-progress-circle--icon-spinner-path-bg\"\n cx=\"20\"\n cy=\"20\"\n r=\"15\"\n fill=\"none\"\n stroke-width=\"4\"\n ></circle>\n <circle\n class=\"seam-progress-circle--icon-spinner-path\"\n cx=\"20\"\n cy=\"20\"\n r=\"15\"\n fill=\"none\"\n transform=\"rotate(-90, 20, 20)\"\n stroke-width=\"4\"\n stroke-miterlimit=\"10\"\n [attr.stroke-dashoffset]=\"p.dashoffset\"\n [style.stroke-dasharray]=\"p.circumference\"\n ></circle>\n <!-- Checkmark -->\n <path\n *ngIf=\"!pending && p.percent === 100\"\n class=\"seam-progress-circle--icon-spinner-path\"\n fill=\"none\"\n stroke-width=\"4\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M 11,22 L 18,28 L 28.5,17\"\n ></path>\n <!-- Hourglass -->\n <!-- `p.percent === 101` is still here for backwards compatability, but planing to be removed. -->\n <path\n *ngIf=\"pending || p.percent === 101\"\n class=\"seam-progress-circle--icon-hourglass\"\n fill=\"none\"\n d=\"M27.3,25.8c-0.2-2.5-1.6-4.6-3.6-5.8c2-1.2,3.4-3.3,3.6-5.8c0.7-0.3,1.2-1,1.2-1.8c0-1.1-0.9-2-2-2h-13\n c-1.1,0-2,0.9-2,2c0,0.8,0.5,1.6,1.2,1.8c0.2,2.5,1.6,4.6,3.6,5.8c-2,1.2-3.4,3.3-3.6,5.8c-0.7,0.3-1.2,1-1.2,1.8c0,1.1,0.9,2,2,2\n h13c1.1,0,2-0.9,2-2C28.6,26.9,28,26.1,27.3,25.8z M24.3,25.7h-8.5c0.3-2.1,2.1-3.6,4.2-3.6S24,23.6,24.3,25.7z M20,18\n c-2.1,0-3.9-1.6-4.2-3.6h8.5C24,16.5,22.2,18,20,18z\"\n ></path>\n </svg>\n </ng-container>\n <div\n class=\"seam-progress-circle--icon-text\"\n *ngIf=\"showText && !pending && p.percent > 0 && p.percent < 100\"\n >\n <span>{{ p.percent }}</span>\n </div>\n </div>\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n computed,\n effect,\n inject,\n input,\n signal,\n} from '@angular/core'\nimport { FormControlStatus } from '@angular/forms'\nimport { Subscription } from 'rxjs'\n\nimport { TheSeamSegmentedProgressBarStep } from './segmented-progress-bar.models'\n\ntype CellState = 'DEFAULT' | 'COMPLETE'\n\n@Component({\n selector: 'seam-segmented-progress-bar-cell',\n template: '',\n host: {\n '[class.bg-light]': '_state() === \"DEFAULT\"',\n '[class.bg-success]': '_state() === \"COMPLETE\"',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SegmentedProgressBarCellComponent {\n private readonly _destroyRef = inject(DestroyRef)\n\n readonly step = input.required<TheSeamSegmentedProgressBarStep>()\n\n private readonly _controlStatus = signal<FormControlStatus | null>(null)\n\n protected readonly _state = computed<CellState>(() => {\n const s = this.step()\n if (s.completed !== undefined) {\n return s.completed ? 'COMPLETE' : 'DEFAULT'\n }\n if (s.control) {\n const status = this._controlStatus()\n if (status === 'VALID' && (s.isCurrent || s.hasVisited)) {\n return 'COMPLETE'\n }\n return 'DEFAULT'\n }\n return 'DEFAULT'\n })\n\n constructor() {\n let sub: Subscription | undefined\n effect(() => {\n const s = this.step()\n sub?.unsubscribe()\n sub = undefined\n if (s.control) {\n this._controlStatus.set(s.control.status)\n sub = s.control.statusChanges.subscribe((status) =>\n this._controlStatus.set(status),\n )\n } else {\n this._controlStatus.set(null)\n }\n })\n this._destroyRef.onDestroy(() => sub?.unsubscribe())\n }\n}\n","import {\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n} from '@angular/core'\n\nimport { TheSeamTooltipDirective } from '@theseam/ui-common/tooltip'\n\nimport { SegmentedProgressBarCellComponent } from './segmented-progress-bar-cell.component'\nimport { TheSeamSegmentedProgressBarStep } from './segmented-progress-bar.models'\n\n@Component({\n selector: 'seam-segmented-progress-bar',\n templateUrl: './segmented-progress-bar.component.html',\n styleUrls: ['./segmented-progress-bar.component.scss'],\n imports: [SegmentedProgressBarCellComponent, TheSeamTooltipDirective],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TheSeamSegmentedProgressBarComponent {\n readonly progressSteps = input<TheSeamSegmentedProgressBarStep[]>([])\n readonly clickable = input(false, { transform: booleanAttribute })\n readonly enableTooltip = input(false, { transform: booleanAttribute })\n\n readonly cellClicked = output<TheSeamSegmentedProgressBarStep>()\n\n onClickProgressCell(step: TheSeamSegmentedProgressBarStep): void {\n if (!this.clickable()) {\n return\n }\n this.cellClicked.emit(step)\n }\n}\n","<div class=\"progress-bar--container\">\n @for (step of progressSteps(); track step.value) {\n <seam-segmented-progress-bar-cell\n [step]=\"step\"\n class=\"progress-bar--cell border\"\n [class.is-clickable]=\"clickable()\"\n (click)=\"onClickProgressCell(step)\"\n [seamTooltip]=\"step.label\"\n placement=\"bottom\"\n container=\"body\"\n [disableTooltip]=\"!enableTooltip()\"\n />\n }\n</div>\n","import { CommonModule } from '@angular/common'\nimport { NgModule } from '@angular/core'\n\nimport { ProgressCircleComponent } from './progress-circle/progress-circle.component'\nimport { TheSeamSegmentedProgressBarComponent } from './segmented-progress-bar/segmented-progress-bar.component'\n\n@NgModule({\n declarations: [ProgressCircleComponent],\n imports: [CommonModule, TheSeamSegmentedProgressBarComponent],\n exports: [ProgressCircleComponent, TheSeamSegmentedProgressBarComponent],\n})\nexport class TheSeamProgressModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,317 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, Injectable, input, booleanAttribute, output, viewChild, effect, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
3
+ import { geoAlbers, geoPath } from 'd3-geo';
4
+ import { select } from 'd3-selection';
5
+ import { feature, mesh } from 'topojson-client';
6
+ import { ComponentHarness } from '@angular/cdk/testing';
7
+
8
+ /** Default URL used when no config is provided. */
9
+ const THE_SEAM_STATES_COUNTIES_MAP_DEFAULT_URL = '/assets/geoData/us.json';
10
+ /** DI token for `TheSeamStatesCountiesMapConfig`. */
11
+ const THE_SEAM_STATES_COUNTIES_MAP_CONFIG = new InjectionToken('THE_SEAM_STATES_COUNTIES_MAP_CONFIG');
12
+ /**
13
+ * Register configuration for the states-counties map.
14
+ *
15
+ * ```ts
16
+ * bootstrapApplication(AppComponent, {
17
+ * providers: [
18
+ * provideStatesCountiesMap({ topologyUrl: '/static/us.json' }),
19
+ * ],
20
+ * })
21
+ * ```
22
+ */
23
+ function provideStatesCountiesMap(config) {
24
+ return { provide: THE_SEAM_STATES_COUNTIES_MAP_CONFIG, useValue: config };
25
+ }
26
+
27
+ /**
28
+ * Loads and caches TopoJSON topology data for the states/counties map.
29
+ *
30
+ * Provided in root so that multiple component instances share a single
31
+ * in-flight/cached request per URL.
32
+ */
33
+ class TheSeamStatesCountiesMapDataService {
34
+ _config = inject(THE_SEAM_STATES_COUNTIES_MAP_CONFIG, {
35
+ optional: true,
36
+ });
37
+ _cache = new Map();
38
+ /**
39
+ * Load the topology from the configured URL (or an explicit override).
40
+ * Concurrent calls for the same URL share a single fetch promise.
41
+ */
42
+ load(url) {
43
+ const resolvedUrl = url ??
44
+ this._config?.topologyUrl ??
45
+ THE_SEAM_STATES_COUNTIES_MAP_DEFAULT_URL;
46
+ const existing = this._cache.get(resolvedUrl);
47
+ if (existing) {
48
+ return existing;
49
+ }
50
+ const promise = fetch(resolvedUrl).then(async (res) => {
51
+ if (!res.ok) {
52
+ throw new Error(`Failed to load topology from ${resolvedUrl}: ${res.status} ${res.statusText}`);
53
+ }
54
+ return (await res.json());
55
+ });
56
+ // Evict on rejection so a transient failure doesn't poison the cache.
57
+ promise.catch(() => this._cache.delete(resolvedUrl));
58
+ this._cache.set(resolvedUrl, promise);
59
+ return promise;
60
+ }
61
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamStatesCountiesMapDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
62
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamStatesCountiesMapDataService, providedIn: 'root' });
63
+ }
64
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamStatesCountiesMapDataService, decorators: [{
65
+ type: Injectable,
66
+ args: [{ providedIn: 'root' }]
67
+ }] });
68
+
69
+ /**
70
+ * Extract the state FIPS prefix from a county FIPS code.
71
+ *
72
+ * County codes are either 4- or 5-digit numeric ids (as strings or numbers
73
+ * depending on the source). The state portion is everything except the last
74
+ * three digits — i.e. 1 or 2 characters.
75
+ */
76
+ function stateIdFromCountyId(countyId) {
77
+ const asString = `${countyId}`;
78
+ return asString.slice(0, asString.length - 3);
79
+ }
80
+ /**
81
+ * Whether the given county id appears in the selection list.
82
+ *
83
+ * Comparison is numeric so that `1001` and `"01001"` are treated as equal,
84
+ * matching the historical behavior of the Cotton app component.
85
+ */
86
+ function isCountySelected(countyId, selectedCountyIds) {
87
+ if (!selectedCountyIds || selectedCountyIds.length === 0) {
88
+ return false;
89
+ }
90
+ const target = Number(countyId);
91
+ for (const id of selectedCountyIds) {
92
+ if (Number(id) === target) {
93
+ return true;
94
+ }
95
+ }
96
+ return false;
97
+ }
98
+
99
+ class TheSeamStatesCountiesMapComponent {
100
+ /** FIPS state code (e.g., `"48"` for Texas). Null/undefined renders nothing. */
101
+ stateNumber = input(undefined, ...(ngDevMode ? [{ debugName: "stateNumber" }] : []));
102
+ /** FIPS county codes to highlight with the `county-selected` class. */
103
+ selectedCountyIds = input([], ...(ngDevMode ? [{ debugName: "selectedCountyIds" }] : []));
104
+ /** Enable pointer interaction (click, enter, leave) on counties. */
105
+ interactive = input(false, ...(ngDevMode ? [{ debugName: "interactive", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
106
+ countyClick = output();
107
+ countyEnter = output();
108
+ countyLeave = output();
109
+ _wrapper = viewChild.required('wrapper');
110
+ _data = inject(TheSeamStatesCountiesMapDataService);
111
+ _topologyPromise = null;
112
+ _lastRenderedState = null;
113
+ _lastRenderedInteractive = null;
114
+ _renderSerial = 0;
115
+ constructor() {
116
+ // Re-render whenever the state number or interactive mode changes.
117
+ effect(() => {
118
+ const state = this.stateNumber() ?? null;
119
+ const inter = this.interactive();
120
+ if (state !== this._lastRenderedState ||
121
+ inter !== this._lastRenderedInteractive) {
122
+ this._lastRenderedState = state;
123
+ this._lastRenderedInteractive = inter;
124
+ void this._render();
125
+ }
126
+ });
127
+ // Re-apply selection classes whenever selection changes (no full re-render).
128
+ effect(() => {
129
+ // Touch the signal so the effect tracks it.
130
+ this.selectedCountyIds();
131
+ this._updateSelectedCounties();
132
+ });
133
+ // Reflow on container resize.
134
+ effect((onCleanup) => {
135
+ const host = this._wrapper().nativeElement;
136
+ const observer = new ResizeObserver(() => void this._render());
137
+ observer.observe(host);
138
+ onCleanup(() => observer.disconnect());
139
+ });
140
+ }
141
+ _loadTopology() {
142
+ if (!this._topologyPromise) {
143
+ this._topologyPromise = this._data.load();
144
+ }
145
+ return this._topologyPromise;
146
+ }
147
+ async _render() {
148
+ const serial = ++this._renderSerial;
149
+ const host = this._wrapper().nativeElement;
150
+ const rect = host.getBoundingClientRect();
151
+ const width = rect.width;
152
+ const height = rect.height;
153
+ select(host).select('svg').remove();
154
+ const state = this.stateNumber();
155
+ if (!state) {
156
+ return;
157
+ }
158
+ const topology = await this._loadTopology();
159
+ if (serial !== this._renderSerial) {
160
+ return;
161
+ }
162
+ const statesLayer = topology.objects['states'];
163
+ const countiesLayer = topology.objects['counties'];
164
+ const states = feature(topology, statesLayer);
165
+ const stateFeature = states.features.find((d) => Number(d.id) === Number(state));
166
+ if (!stateFeature) {
167
+ return;
168
+ }
169
+ const projection = geoAlbers();
170
+ const path = geoPath().projection(projection);
171
+ projection.scale(1).translate([0, 0]);
172
+ const b = path.bounds(stateFeature);
173
+ const s = 0.95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height);
174
+ const t = [
175
+ (width - s * (b[1][0] + b[0][0])) / 2,
176
+ (height - s * (b[1][1] + b[0][1])) / 2,
177
+ ];
178
+ projection.scale(s).translate(t);
179
+ const svg = select(host)
180
+ .append('svg')
181
+ .attr('width', width)
182
+ .attr('height', height);
183
+ svg
184
+ .append('path')
185
+ .datum(mesh(topology, statesLayer, (a, b1) => a !== b1))
186
+ .attr('class', 'mesh')
187
+ .attr('d', path);
188
+ svg
189
+ .append('path')
190
+ .datum(stateFeature)
191
+ .attr('class', 'outline')
192
+ .attr('d', path)
193
+ .attr('id', 'land');
194
+ svg
195
+ .append('clipPath')
196
+ .attr('id', 'clip-land')
197
+ .append('use')
198
+ .attr('xlink:href', '#land');
199
+ const counties = feature(topology, countiesLayer);
200
+ const stateNum = `${parseInt(state, 10)}`;
201
+ const stateCounties = counties.features.filter((d) => stateIdFromCountyId(d.id) === stateNum);
202
+ const isInteractive = this.interactive();
203
+ const countyPaths = svg
204
+ .selectAll('path[county-id]')
205
+ .data(stateCounties)
206
+ .enter()
207
+ .append('path')
208
+ .attr('d', path)
209
+ .attr('county-id', (d) => `${d.id}`.padStart(5, '0'));
210
+ if (isInteractive) {
211
+ countyPaths
212
+ .on('click', (_event, d) => {
213
+ this.countyClick.emit({
214
+ id: `${d.id}`.padStart(5, '0'),
215
+ feature: d,
216
+ });
217
+ })
218
+ .on('mouseenter', (_event, d) => {
219
+ select(_event.currentTarget).classed('county-hover', true);
220
+ this.countyEnter.emit({
221
+ id: `${d.id}`.padStart(5, '0'),
222
+ feature: d,
223
+ });
224
+ })
225
+ .on('mouseleave', (_event, d) => {
226
+ select(_event.currentTarget).classed('county-hover', false);
227
+ this.countyLeave.emit({
228
+ id: `${d.id}`.padStart(5, '0'),
229
+ feature: d,
230
+ });
231
+ });
232
+ }
233
+ this._updateSelectedCounties();
234
+ }
235
+ _updateSelectedCounties() {
236
+ const host = this._wrapper().nativeElement;
237
+ const selected = this.selectedCountyIds();
238
+ select(host)
239
+ .select('svg')
240
+ .selectAll('path[county-id]')
241
+ .attr('class', (d) => isCountySelected(d.id, selected)
242
+ ? 'county-selected'
243
+ : 'county');
244
+ }
245
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamStatesCountiesMapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
246
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.15", type: TheSeamStatesCountiesMapComponent, isStandalone: true, selector: "seam-states-counties-map", inputs: { stateNumber: { classPropertyName: "stateNumber", publicName: "stateNumber", isSignal: true, isRequired: false, transformFunction: null }, selectedCountyIds: { classPropertyName: "selectedCountyIds", publicName: "selectedCountyIds", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { countyClick: "countyClick", countyEnter: "countyEnter", countyLeave: "countyLeave" }, host: { properties: { "class.seam-interactive": "interactive()" } }, viewQueries: [{ propertyName: "_wrapper", first: true, predicate: ["wrapper"], descendants: true, isSignal: true }], ngImport: i0, template: `<div #wrapper class="states-counties-map-wrapper"></div>`, isInline: true, styles: ["seam-states-counties-map{display:block;height:100%}seam-states-counties-map .states-counties-map-wrapper{width:100%;height:100%}seam-states-counties-map .outline{fill:none;stroke:#000;stroke-width:1.5px}seam-states-counties-map .mesh{fill:none;stroke:#fff;stroke-width:.5px;stroke-linejoin:round}seam-states-counties-map path{stroke-width:.5px;fill:none;stroke:#000}seam-states-counties-map .county{fill:transparent}seam-states-counties-map .county-selected{fill:#00f}seam-states-counties-map.seam-interactive .county,seam-states-counties-map.seam-interactive .county-selected{cursor:pointer}seam-states-counties-map .county-hover:not(.county-selected){fill:#0000001a}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
247
+ }
248
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamStatesCountiesMapComponent, decorators: [{
249
+ type: Component,
250
+ args: [{ selector: 'seam-states-counties-map', template: `<div #wrapper class="states-counties-map-wrapper"></div>`, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.seam-interactive]': 'interactive()' }, styles: ["seam-states-counties-map{display:block;height:100%}seam-states-counties-map .states-counties-map-wrapper{width:100%;height:100%}seam-states-counties-map .outline{fill:none;stroke:#000;stroke-width:1.5px}seam-states-counties-map .mesh{fill:none;stroke:#fff;stroke-width:.5px;stroke-linejoin:round}seam-states-counties-map path{stroke-width:.5px;fill:none;stroke:#000}seam-states-counties-map .county{fill:transparent}seam-states-counties-map .county-selected{fill:#00f}seam-states-counties-map.seam-interactive .county,seam-states-counties-map.seam-interactive .county-selected{cursor:pointer}seam-states-counties-map .county-hover:not(.county-selected){fill:#0000001a}\n"] }]
251
+ }], ctorParameters: () => [], propDecorators: { stateNumber: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateNumber", required: false }] }], selectedCountyIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedCountyIds", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], countyClick: [{ type: i0.Output, args: ["countyClick"] }], countyEnter: [{ type: i0.Output, args: ["countyEnter"] }], countyLeave: [{ type: i0.Output, args: ["countyLeave"] }], _wrapper: [{ type: i0.ViewChild, args: ['wrapper', { isSignal: true }] }] } });
252
+
253
+ /**
254
+ * Harness for `TheSeamStatesCountiesMapComponent`. Designed for use in both
255
+ * TestBed and Storybook (via `@marklb/storybook-harness`).
256
+ */
257
+ class TheSeamStatesCountiesMapHarness extends ComponentHarness {
258
+ static hostSelector = 'seam-states-counties-map';
259
+ _wrapper = this.locatorFor('.states-counties-map-wrapper');
260
+ _svg = this.locatorForOptional('svg');
261
+ /** Whether the SVG has been rendered yet. */
262
+ async hasRendered() {
263
+ return (await this._svg()) !== null;
264
+ }
265
+ /** Returns all county path elements that have been rendered. */
266
+ async getCountyPaths() {
267
+ return this.locatorForAll('path[county-id]')();
268
+ }
269
+ /** Returns the path for a single county by its FIPS id, or null. */
270
+ async getCountyPath(countyId) {
271
+ const matches = await this.locatorForAll(`path[county-id="${countyId}"]`)();
272
+ return matches[0] ?? null;
273
+ }
274
+ /** Ids of every county currently marked `county-selected`. */
275
+ async getSelectedCountyIds() {
276
+ const paths = await this.locatorForAll('path.county-selected')();
277
+ const ids = await Promise.all(paths.map((p) => p.getAttribute('county-id')));
278
+ return ids.filter((id) => id !== null);
279
+ }
280
+ /** Click a county by FIPS id. Throws if the county is not rendered. */
281
+ async clickCounty(countyId) {
282
+ const path = await this.getCountyPath(countyId);
283
+ if (!path) {
284
+ throw new Error(`TheSeamStatesCountiesMapHarness.clickCounty: county ${countyId} is not rendered`);
285
+ }
286
+ await path.click();
287
+ }
288
+ /** Simulate pointer entering a county area. Throws if the county is not rendered. */
289
+ async enterCounty(countyId) {
290
+ const path = await this.getCountyPath(countyId);
291
+ if (!path) {
292
+ throw new Error(`TheSeamStatesCountiesMapHarness.enterCounty: county ${countyId} is not rendered`);
293
+ }
294
+ await path.hover();
295
+ }
296
+ /** Simulate pointer leaving a county area by hovering away from it. */
297
+ async leaveCounty(countyId) {
298
+ const path = await this.getCountyPath(countyId);
299
+ if (!path) {
300
+ throw new Error(`TheSeamStatesCountiesMapHarness.leaveCounty: county ${countyId} is not rendered`);
301
+ }
302
+ // Move pointer to the wrapper (outside any county) to trigger mouseleave.
303
+ await (await this.locatorFor('.states-counties-map-wrapper')()).hover();
304
+ }
305
+ /** Rendered viewport dimensions, for layout-sensitive assertions. */
306
+ async getWrapperSize() {
307
+ const rect = await (await this._wrapper()).getDimensions();
308
+ return { width: rect.width, height: rect.height };
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Generated bundle index. Do not edit.
314
+ */
315
+
316
+ export { THE_SEAM_STATES_COUNTIES_MAP_CONFIG, THE_SEAM_STATES_COUNTIES_MAP_DEFAULT_URL, TheSeamStatesCountiesMapComponent, TheSeamStatesCountiesMapDataService, TheSeamStatesCountiesMapHarness, provideStatesCountiesMap };
317
+ //# sourceMappingURL=theseam-ui-common-states-counties-map.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theseam-ui-common-states-counties-map.mjs","sources":["../../../projects/ui-common/states-counties-map/states-counties-map-config.ts","../../../projects/ui-common/states-counties-map/states-counties-map-data.service.ts","../../../projects/ui-common/states-counties-map/states-counties-map.helpers.ts","../../../projects/ui-common/states-counties-map/states-counties-map.component.ts","../../../projects/ui-common/states-counties-map/testing/states-counties-map.harness.ts","../../../projects/ui-common/states-counties-map/theseam-ui-common-states-counties-map.ts"],"sourcesContent":["import { InjectionToken, Provider } from '@angular/core'\n\n/** Configuration for `TheSeamStatesCountiesMapComponent`. */\nexport interface TheSeamStatesCountiesMapConfig {\n /**\n * URL to the TopoJSON topology file.\n *\n * Defaults to `/assets/geoData/us.json` when the token is not provided.\n * The file is not shipped with the library — each consuming app must\n * place a compatible topology at this path or provide a different URL\n * via `provideStatesCountiesMap({ topologyUrl })`.\n */\n readonly topologyUrl?: string\n}\n\n/** Default URL used when no config is provided. */\nexport const THE_SEAM_STATES_COUNTIES_MAP_DEFAULT_URL =\n '/assets/geoData/us.json'\n\n/** DI token for `TheSeamStatesCountiesMapConfig`. */\nexport const THE_SEAM_STATES_COUNTIES_MAP_CONFIG =\n new InjectionToken<TheSeamStatesCountiesMapConfig>(\n 'THE_SEAM_STATES_COUNTIES_MAP_CONFIG',\n )\n\n/**\n * Register configuration for the states-counties map.\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideStatesCountiesMap({ topologyUrl: '/static/us.json' }),\n * ],\n * })\n * ```\n */\nexport function provideStatesCountiesMap(\n config: TheSeamStatesCountiesMapConfig,\n): Provider {\n return { provide: THE_SEAM_STATES_COUNTIES_MAP_CONFIG, useValue: config }\n}\n","import { inject, Injectable } from '@angular/core'\nimport type { Topology } from 'topojson-specification'\n\nimport {\n THE_SEAM_STATES_COUNTIES_MAP_CONFIG,\n THE_SEAM_STATES_COUNTIES_MAP_DEFAULT_URL,\n} from './states-counties-map-config'\n\n/**\n * Loads and caches TopoJSON topology data for the states/counties map.\n *\n * Provided in root so that multiple component instances share a single\n * in-flight/cached request per URL.\n */\n@Injectable({ providedIn: 'root' })\nexport class TheSeamStatesCountiesMapDataService {\n private readonly _config = inject(THE_SEAM_STATES_COUNTIES_MAP_CONFIG, {\n optional: true,\n })\n\n private readonly _cache = new Map<string, Promise<Topology>>()\n\n /**\n * Load the topology from the configured URL (or an explicit override).\n * Concurrent calls for the same URL share a single fetch promise.\n */\n load(url?: string): Promise<Topology> {\n const resolvedUrl =\n url ??\n this._config?.topologyUrl ??\n THE_SEAM_STATES_COUNTIES_MAP_DEFAULT_URL\n\n const existing = this._cache.get(resolvedUrl)\n if (existing) {\n return existing\n }\n\n const promise = fetch(resolvedUrl).then(async (res) => {\n if (!res.ok) {\n throw new Error(\n `Failed to load topology from ${resolvedUrl}: ${res.status} ${res.statusText}`,\n )\n }\n return (await res.json()) as Topology\n })\n\n // Evict on rejection so a transient failure doesn't poison the cache.\n promise.catch(() => this._cache.delete(resolvedUrl))\n this._cache.set(resolvedUrl, promise)\n return promise\n }\n}\n","/**\n * Extract the state FIPS prefix from a county FIPS code.\n *\n * County codes are either 4- or 5-digit numeric ids (as strings or numbers\n * depending on the source). The state portion is everything except the last\n * three digits — i.e. 1 or 2 characters.\n */\nexport function stateIdFromCountyId(countyId: string | number): string {\n const asString = `${countyId}`\n return asString.slice(0, asString.length - 3)\n}\n\n/**\n * Whether the given county id appears in the selection list.\n *\n * Comparison is numeric so that `1001` and `\"01001\"` are treated as equal,\n * matching the historical behavior of the Cotton app component.\n */\nexport function isCountySelected(\n countyId: string | number,\n selectedCountyIds: readonly (string | number)[] | null | undefined,\n): boolean {\n if (!selectedCountyIds || selectedCountyIds.length === 0) {\n return false\n }\n const target = Number(countyId)\n for (const id of selectedCountyIds) {\n if (Number(id) === target) {\n return true\n }\n }\n return false\n}\n","import {\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n effect,\n inject,\n input,\n output,\n viewChild,\n ViewEncapsulation,\n} from '@angular/core'\nimport type { Feature, FeatureCollection, Geometry } from 'geojson'\nimport { geoAlbers, geoPath, type GeoPath } from 'd3-geo'\nimport { select, type Selection } from 'd3-selection'\nimport { feature as topoFeature, mesh as topoMesh } from 'topojson-client'\nimport type { GeometryCollection, Topology } from 'topojson-specification'\n\nimport { TheSeamStatesCountiesMapCountyEvent } from './states-counties-map.models'\nimport { TheSeamStatesCountiesMapDataService } from './states-counties-map-data.service'\nimport {\n isCountySelected,\n stateIdFromCountyId,\n} from './states-counties-map.helpers'\n\n@Component({\n selector: 'seam-states-counties-map',\n template: `<div #wrapper class=\"states-counties-map-wrapper\"></div>`,\n styleUrls: ['./states-counties-map.component.scss'],\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { '[class.seam-interactive]': 'interactive()' },\n})\nexport class TheSeamStatesCountiesMapComponent {\n /** FIPS state code (e.g., `\"48\"` for Texas). Null/undefined renders nothing. */\n readonly stateNumber = input<string | null | undefined>(undefined)\n\n /** FIPS county codes to highlight with the `county-selected` class. */\n readonly selectedCountyIds = input<readonly string[]>([])\n\n /** Enable pointer interaction (click, enter, leave) on counties. */\n readonly interactive = input(false, { transform: booleanAttribute })\n\n readonly countyClick = output<TheSeamStatesCountiesMapCountyEvent>()\n readonly countyEnter = output<TheSeamStatesCountiesMapCountyEvent>()\n readonly countyLeave = output<TheSeamStatesCountiesMapCountyEvent>()\n\n private readonly _wrapper =\n viewChild.required<ElementRef<HTMLDivElement>>('wrapper')\n\n private readonly _data = inject(TheSeamStatesCountiesMapDataService)\n\n private _topologyPromise: Promise<Topology> | null = null\n private _lastRenderedState: string | null = null\n private _lastRenderedInteractive: boolean | null = null\n private _renderSerial = 0\n\n constructor() {\n // Re-render whenever the state number or interactive mode changes.\n effect(() => {\n const state = this.stateNumber() ?? null\n const inter = this.interactive()\n if (\n state !== this._lastRenderedState ||\n inter !== this._lastRenderedInteractive\n ) {\n this._lastRenderedState = state\n this._lastRenderedInteractive = inter\n void this._render()\n }\n })\n\n // Re-apply selection classes whenever selection changes (no full re-render).\n effect(() => {\n // Touch the signal so the effect tracks it.\n this.selectedCountyIds()\n this._updateSelectedCounties()\n })\n\n // Reflow on container resize.\n effect((onCleanup) => {\n const host = this._wrapper().nativeElement\n const observer = new ResizeObserver(() => void this._render())\n observer.observe(host)\n onCleanup(() => observer.disconnect())\n })\n }\n\n private _loadTopology(): Promise<Topology> {\n if (!this._topologyPromise) {\n this._topologyPromise = this._data.load()\n }\n return this._topologyPromise\n }\n\n private async _render(): Promise<void> {\n const serial = ++this._renderSerial\n const host = this._wrapper().nativeElement\n const rect = host.getBoundingClientRect()\n const width = rect.width\n const height = rect.height\n\n select(host).select('svg').remove()\n\n const state = this.stateNumber()\n if (!state) {\n return\n }\n\n const topology = await this._loadTopology()\n if (serial !== this._renderSerial) {\n return\n }\n\n const statesLayer = topology.objects['states'] as GeometryCollection\n const countiesLayer = topology.objects['counties'] as GeometryCollection\n\n const states = topoFeature(\n topology,\n statesLayer,\n ) as FeatureCollection<Geometry>\n\n const stateFeature = states.features.find(\n (d) => Number(d.id) === Number(state),\n )\n if (!stateFeature) {\n return\n }\n\n const projection = geoAlbers()\n const path: GeoPath = geoPath().projection(projection)\n\n projection.scale(1).translate([0, 0])\n const b = path.bounds(stateFeature)\n const s =\n 0.95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height)\n const t: [number, number] = [\n (width - s * (b[1][0] + b[0][0])) / 2,\n (height - s * (b[1][1] + b[0][1])) / 2,\n ]\n projection.scale(s).translate(t)\n\n const svg: Selection<SVGSVGElement, unknown, null, undefined> = select(host)\n .append('svg')\n .attr('width', width)\n .attr('height', height)\n\n svg\n .append('path')\n .datum(topoMesh(topology, statesLayer, (a, b1) => a !== b1))\n .attr('class', 'mesh')\n .attr('d', path as unknown as string)\n\n svg\n .append('path')\n .datum(stateFeature)\n .attr('class', 'outline')\n .attr('d', path as unknown as string)\n .attr('id', 'land')\n\n svg\n .append('clipPath')\n .attr('id', 'clip-land')\n .append('use')\n .attr('xlink:href', '#land')\n\n const counties = topoFeature(\n topology,\n countiesLayer,\n ) as FeatureCollection<Geometry>\n\n const stateNum = `${parseInt(state, 10)}`\n const stateCounties = counties.features.filter(\n (d) => stateIdFromCountyId(d.id as string | number) === stateNum,\n )\n\n const isInteractive = this.interactive()\n\n const countyPaths = svg\n .selectAll<SVGPathElement, Feature<Geometry>>('path[county-id]')\n .data(stateCounties)\n .enter()\n .append('path')\n .attr('d', path as unknown as string)\n .attr('county-id', (d) => `${d.id}`.padStart(5, '0'))\n\n if (isInteractive) {\n countyPaths\n .on('click', (_event, d) => {\n this.countyClick.emit({\n id: `${d.id}`.padStart(5, '0'),\n feature: d,\n })\n })\n .on('mouseenter', (_event: MouseEvent, d) => {\n select(_event.currentTarget as SVGPathElement).classed(\n 'county-hover',\n true,\n )\n this.countyEnter.emit({\n id: `${d.id}`.padStart(5, '0'),\n feature: d,\n })\n })\n .on('mouseleave', (_event: MouseEvent, d) => {\n select(_event.currentTarget as SVGPathElement).classed(\n 'county-hover',\n false,\n )\n this.countyLeave.emit({\n id: `${d.id}`.padStart(5, '0'),\n feature: d,\n })\n })\n }\n\n this._updateSelectedCounties()\n }\n\n private _updateSelectedCounties(): void {\n const host = this._wrapper().nativeElement\n const selected = this.selectedCountyIds()\n select(host)\n .select<SVGSVGElement>('svg')\n .selectAll<SVGPathElement, Feature<Geometry>>('path[county-id]')\n .attr('class', (d) =>\n isCountySelected(d.id as string | number, selected)\n ? 'county-selected'\n : 'county',\n )\n }\n}\n","import { ComponentHarness, TestElement } from '@angular/cdk/testing'\n\n/**\n * Harness for `TheSeamStatesCountiesMapComponent`. Designed for use in both\n * TestBed and Storybook (via `@marklb/storybook-harness`).\n */\nexport class TheSeamStatesCountiesMapHarness extends ComponentHarness {\n static hostSelector = 'seam-states-counties-map'\n\n private readonly _wrapper = this.locatorFor('.states-counties-map-wrapper')\n private readonly _svg = this.locatorForOptional('svg')\n\n /** Whether the SVG has been rendered yet. */\n async hasRendered(): Promise<boolean> {\n return (await this._svg()) !== null\n }\n\n /** Returns all county path elements that have been rendered. */\n async getCountyPaths(): Promise<TestElement[]> {\n return this.locatorForAll('path[county-id]')()\n }\n\n /** Returns the path for a single county by its FIPS id, or null. */\n async getCountyPath(countyId: string): Promise<TestElement | null> {\n const matches = await this.locatorForAll(`path[county-id=\"${countyId}\"]`)()\n return matches[0] ?? null\n }\n\n /** Ids of every county currently marked `county-selected`. */\n async getSelectedCountyIds(): Promise<string[]> {\n const paths = await this.locatorForAll('path.county-selected')()\n const ids = await Promise.all(paths.map((p) => p.getAttribute('county-id')))\n return ids.filter((id): id is string => id !== null)\n }\n\n /** Click a county by FIPS id. Throws if the county is not rendered. */\n async clickCounty(countyId: string): Promise<void> {\n const path = await this.getCountyPath(countyId)\n if (!path) {\n throw new Error(\n `TheSeamStatesCountiesMapHarness.clickCounty: county ${countyId} is not rendered`,\n )\n }\n await path.click()\n }\n\n /** Simulate pointer entering a county area. Throws if the county is not rendered. */\n async enterCounty(countyId: string): Promise<void> {\n const path = await this.getCountyPath(countyId)\n if (!path) {\n throw new Error(\n `TheSeamStatesCountiesMapHarness.enterCounty: county ${countyId} is not rendered`,\n )\n }\n await path.hover()\n }\n\n /** Simulate pointer leaving a county area by hovering away from it. */\n async leaveCounty(countyId: string): Promise<void> {\n const path = await this.getCountyPath(countyId)\n if (!path) {\n throw new Error(\n `TheSeamStatesCountiesMapHarness.leaveCounty: county ${countyId} is not rendered`,\n )\n }\n // Move pointer to the wrapper (outside any county) to trigger mouseleave.\n await (await this.locatorFor('.states-counties-map-wrapper')()).hover()\n }\n\n /** Rendered viewport dimensions, for layout-sensitive assertions. */\n async getWrapperSize(): Promise<{ width: number; height: number }> {\n const rect = await (await this._wrapper()).getDimensions()\n return { width: rect.width, height: rect.height }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["topoFeature","topoMesh"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theseam/ui-common",
3
- "version": "1.0.2-beta.48",
3
+ "version": "1.0.2-beta.54",
4
4
  "peerDependencies": {
5
5
  "@angular/cdk": "^20.2.3",
6
6
  "@angular/common": "^20.3.0",
@@ -267,6 +267,10 @@
267
267
  "types": "./shared/index.d.ts",
268
268
  "default": "./fesm2022/theseam-ui-common-shared.mjs"
269
269
  },
270
+ "./states-counties-map": {
271
+ "types": "./states-counties-map/index.d.ts",
272
+ "default": "./fesm2022/theseam-ui-common-states-counties-map.mjs"
273
+ },
270
274
  "./storage": {
271
275
  "types": "./storage/index.d.ts",
272
276
  "default": "./fesm2022/theseam-ui-common-storage.mjs"
@@ -1,7 +1,26 @@
1
+ import { ComponentHarness } from '@angular/cdk/testing';
1
2
  import { BooleanInput, NumberInput } from '@angular/cdk/coercion';
2
3
  import * as i0 from '@angular/core';
4
+ import { AbstractControl } from '@angular/forms';
3
5
  import * as i2 from '@angular/common';
4
6
 
7
+ /** Harness for a single cell inside `TheSeamSegmentedProgressBarComponent`. */
8
+ declare class TheSeamSegmentedProgressBarCellHarness extends ComponentHarness {
9
+ static hostSelector: string;
10
+ /** The visual state of the cell, derived from its host classes. */
11
+ getState(): Promise<'default' | 'complete'>;
12
+ click(): Promise<void>;
13
+ }
14
+ /** Harness for `TheSeamSegmentedProgressBarComponent`. */
15
+ declare class TheSeamSegmentedProgressBarHarness extends ComponentHarness {
16
+ static hostSelector: string;
17
+ private readonly _cells;
18
+ /** Gets harnesses for every rendered cell, in order. */
19
+ getCells(): Promise<TheSeamSegmentedProgressBarCellHarness[]>;
20
+ /** Clicks the cell at the given zero-based index. */
21
+ clickCell(index: number): Promise<void>;
22
+ }
23
+
5
24
  interface IProgressInfo {
6
25
  dashoffset: number;
7
26
  circumference: number;
@@ -29,10 +48,48 @@ declare class ProgressCircleComponent {
29
48
  static ɵcmp: i0.ɵɵComponentDeclaration<ProgressCircleComponent, "seam-progress-circle", never, { "fillBackground": { "alias": "fillBackground"; "required": false; }; "showText": { "alias": "showText"; "required": false; }; "hiddenOnEmpty": { "alias": "hiddenOnEmpty"; "required": false; }; "pending": { "alias": "pending"; "required": false; }; "percentage": { "alias": "percentage"; "required": false; }; }, {}, never, never, false, never>;
30
49
  }
31
50
 
51
+ /**
52
+ * Describes a single step rendered by `TheSeamSegmentedProgressBarComponent`.
53
+ *
54
+ * A step's visual state is derived from either an explicit `completed` flag
55
+ * or the validity of an injected `control`. When a `control` is provided,
56
+ * the step is considered visually complete only after it has been visited
57
+ * (either `isCurrent` or `hasVisited` is true) to avoid marking steps as
58
+ * done before the user has seen them.
59
+ */
60
+ interface TheSeamSegmentedProgressBarStep {
61
+ /** Tooltip label shown on hover when `enableTooltip` is true. */
62
+ label: string;
63
+ /** Unique identifier for the step — used as the `@for` track key. */
64
+ value: string;
65
+ /**
66
+ * Explicit completion flag. When set, takes precedence over `control`.
67
+ * Leave undefined to derive state from `control` instead.
68
+ */
69
+ completed?: boolean;
70
+ /** Optional form control whose validity drives state when `completed` is unset. */
71
+ control?: AbstractControl;
72
+ /** Whether this step is the currently-active step. */
73
+ isCurrent?: boolean;
74
+ /** Whether the user has previously visited this step. */
75
+ hasVisited?: boolean;
76
+ }
77
+
78
+ declare class TheSeamSegmentedProgressBarComponent {
79
+ readonly progressSteps: i0.InputSignal<TheSeamSegmentedProgressBarStep[]>;
80
+ readonly clickable: i0.InputSignalWithTransform<boolean, unknown>;
81
+ readonly enableTooltip: i0.InputSignalWithTransform<boolean, unknown>;
82
+ readonly cellClicked: i0.OutputEmitterRef<TheSeamSegmentedProgressBarStep>;
83
+ onClickProgressCell(step: TheSeamSegmentedProgressBarStep): void;
84
+ static ɵfac: i0.ɵɵFactoryDeclaration<TheSeamSegmentedProgressBarComponent, never>;
85
+ static ɵcmp: i0.ɵɵComponentDeclaration<TheSeamSegmentedProgressBarComponent, "seam-segmented-progress-bar", never, { "progressSteps": { "alias": "progressSteps"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "enableTooltip": { "alias": "enableTooltip"; "required": false; "isSignal": true; }; }, { "cellClicked": "cellClicked"; }, never, never, true, never>;
86
+ }
87
+
32
88
  declare class TheSeamProgressModule {
33
89
  static ɵfac: i0.ɵɵFactoryDeclaration<TheSeamProgressModule, never>;
34
- static ɵmod: i0.ɵɵNgModuleDeclaration<TheSeamProgressModule, [typeof ProgressCircleComponent], [typeof i2.CommonModule], [typeof ProgressCircleComponent]>;
90
+ static ɵmod: i0.ɵɵNgModuleDeclaration<TheSeamProgressModule, [typeof ProgressCircleComponent], [typeof i2.CommonModule, typeof TheSeamSegmentedProgressBarComponent], [typeof ProgressCircleComponent, typeof TheSeamSegmentedProgressBarComponent]>;
35
91
  static ɵinj: i0.ɵɵInjectorDeclaration<TheSeamProgressModule>;
36
92
  }
37
93
 
38
- export { ProgressCircleComponent, TheSeamProgressModule, calcDashoffset };
94
+ export { ProgressCircleComponent, TheSeamProgressModule, TheSeamSegmentedProgressBarCellHarness, TheSeamSegmentedProgressBarComponent, TheSeamSegmentedProgressBarHarness, calcDashoffset };
95
+ export type { TheSeamSegmentedProgressBarStep };
@@ -0,0 +1,120 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { InjectionToken, Provider } from '@angular/core';
3
+ import { Feature, Geometry } from 'geojson';
4
+ import { Topology } from 'topojson-specification';
5
+ import { ComponentHarness, TestElement } from '@angular/cdk/testing';
6
+
7
+ /**
8
+ * Payload emitted by `countyClick` / `countyEnter` / `countyLeave` outputs.
9
+ *
10
+ * `id` is the FIPS county code as a string (e.g., `"01001"`), matching the
11
+ * format consumers pass to `selectedCountyIds`. `feature` is the raw
12
+ * GeoJSON feature produced by `topojson.feature(...)`.
13
+ */
14
+ interface TheSeamStatesCountiesMapCountyEvent {
15
+ readonly id: string;
16
+ readonly feature: Feature<Geometry>;
17
+ }
18
+
19
+ declare class TheSeamStatesCountiesMapComponent {
20
+ /** FIPS state code (e.g., `"48"` for Texas). Null/undefined renders nothing. */
21
+ readonly stateNumber: _angular_core.InputSignal<string | null | undefined>;
22
+ /** FIPS county codes to highlight with the `county-selected` class. */
23
+ readonly selectedCountyIds: _angular_core.InputSignal<readonly string[]>;
24
+ /** Enable pointer interaction (click, enter, leave) on counties. */
25
+ readonly interactive: _angular_core.InputSignalWithTransform<boolean, unknown>;
26
+ readonly countyClick: _angular_core.OutputEmitterRef<TheSeamStatesCountiesMapCountyEvent>;
27
+ readonly countyEnter: _angular_core.OutputEmitterRef<TheSeamStatesCountiesMapCountyEvent>;
28
+ readonly countyLeave: _angular_core.OutputEmitterRef<TheSeamStatesCountiesMapCountyEvent>;
29
+ private readonly _wrapper;
30
+ private readonly _data;
31
+ private _topologyPromise;
32
+ private _lastRenderedState;
33
+ private _lastRenderedInteractive;
34
+ private _renderSerial;
35
+ constructor();
36
+ private _loadTopology;
37
+ private _render;
38
+ private _updateSelectedCounties;
39
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<TheSeamStatesCountiesMapComponent, never>;
40
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<TheSeamStatesCountiesMapComponent, "seam-states-counties-map", never, { "stateNumber": { "alias": "stateNumber"; "required": false; "isSignal": true; }; "selectedCountyIds": { "alias": "selectedCountyIds"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; }, { "countyClick": "countyClick"; "countyEnter": "countyEnter"; "countyLeave": "countyLeave"; }, never, never, true, never>;
41
+ }
42
+
43
+ /** Configuration for `TheSeamStatesCountiesMapComponent`. */
44
+ interface TheSeamStatesCountiesMapConfig {
45
+ /**
46
+ * URL to the TopoJSON topology file.
47
+ *
48
+ * Defaults to `/assets/geoData/us.json` when the token is not provided.
49
+ * The file is not shipped with the library — each consuming app must
50
+ * place a compatible topology at this path or provide a different URL
51
+ * via `provideStatesCountiesMap({ topologyUrl })`.
52
+ */
53
+ readonly topologyUrl?: string;
54
+ }
55
+ /** Default URL used when no config is provided. */
56
+ declare const THE_SEAM_STATES_COUNTIES_MAP_DEFAULT_URL = "/assets/geoData/us.json";
57
+ /** DI token for `TheSeamStatesCountiesMapConfig`. */
58
+ declare const THE_SEAM_STATES_COUNTIES_MAP_CONFIG: InjectionToken<TheSeamStatesCountiesMapConfig>;
59
+ /**
60
+ * Register configuration for the states-counties map.
61
+ *
62
+ * ```ts
63
+ * bootstrapApplication(AppComponent, {
64
+ * providers: [
65
+ * provideStatesCountiesMap({ topologyUrl: '/static/us.json' }),
66
+ * ],
67
+ * })
68
+ * ```
69
+ */
70
+ declare function provideStatesCountiesMap(config: TheSeamStatesCountiesMapConfig): Provider;
71
+
72
+ /**
73
+ * Loads and caches TopoJSON topology data for the states/counties map.
74
+ *
75
+ * Provided in root so that multiple component instances share a single
76
+ * in-flight/cached request per URL.
77
+ */
78
+ declare class TheSeamStatesCountiesMapDataService {
79
+ private readonly _config;
80
+ private readonly _cache;
81
+ /**
82
+ * Load the topology from the configured URL (or an explicit override).
83
+ * Concurrent calls for the same URL share a single fetch promise.
84
+ */
85
+ load(url?: string): Promise<Topology>;
86
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<TheSeamStatesCountiesMapDataService, never>;
87
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<TheSeamStatesCountiesMapDataService>;
88
+ }
89
+
90
+ /**
91
+ * Harness for `TheSeamStatesCountiesMapComponent`. Designed for use in both
92
+ * TestBed and Storybook (via `@marklb/storybook-harness`).
93
+ */
94
+ declare class TheSeamStatesCountiesMapHarness extends ComponentHarness {
95
+ static hostSelector: string;
96
+ private readonly _wrapper;
97
+ private readonly _svg;
98
+ /** Whether the SVG has been rendered yet. */
99
+ hasRendered(): Promise<boolean>;
100
+ /** Returns all county path elements that have been rendered. */
101
+ getCountyPaths(): Promise<TestElement[]>;
102
+ /** Returns the path for a single county by its FIPS id, or null. */
103
+ getCountyPath(countyId: string): Promise<TestElement | null>;
104
+ /** Ids of every county currently marked `county-selected`. */
105
+ getSelectedCountyIds(): Promise<string[]>;
106
+ /** Click a county by FIPS id. Throws if the county is not rendered. */
107
+ clickCounty(countyId: string): Promise<void>;
108
+ /** Simulate pointer entering a county area. Throws if the county is not rendered. */
109
+ enterCounty(countyId: string): Promise<void>;
110
+ /** Simulate pointer leaving a county area by hovering away from it. */
111
+ leaveCounty(countyId: string): Promise<void>;
112
+ /** Rendered viewport dimensions, for layout-sensitive assertions. */
113
+ getWrapperSize(): Promise<{
114
+ width: number;
115
+ height: number;
116
+ }>;
117
+ }
118
+
119
+ export { THE_SEAM_STATES_COUNTIES_MAP_CONFIG, THE_SEAM_STATES_COUNTIES_MAP_DEFAULT_URL, TheSeamStatesCountiesMapComponent, TheSeamStatesCountiesMapDataService, TheSeamStatesCountiesMapHarness, provideStatesCountiesMap };
120
+ export type { TheSeamStatesCountiesMapConfig, TheSeamStatesCountiesMapCountyEvent };
@@ -0,0 +1,3 @@
1
+ {
2
+ "module": "../fesm2022/theseam-ui-common-states-counties-map.mjs"
3
+ }