@firestitch/report 18.21.1 → 18.21.3

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.
@@ -4,7 +4,7 @@ import * as echarts from 'echarts/core';
4
4
  export { echarts as default };
5
5
  import { LabelLayout } from 'echarts/features';
6
6
  import { CanvasRenderer } from 'echarts/renderers';
7
- import { R as REPORT_CHART_COLORS_CSS, a as REPORT_CHART_FONT_FAMILY, b as REPORT_AXIS_LABEL_FONT_SIZE } from './firestitch-report-firestitch-report-cys9XusN.mjs';
7
+ import { R as REPORT_CHART_COLORS_CSS, a as REPORT_CHART_FONT_FAMILY, b as REPORT_AXIS_LABEL_FONT_SIZE } from './firestitch-report-firestitch-report-CfvBTMrG.mjs';
8
8
 
9
9
  // Tree-shaken ECharts build for the reports renderer: only the chart types and
10
10
  // components the option-builder emits are registered, keeping the lazy chunk
@@ -63,4 +63,4 @@ echarts.registerTheme('report', {
63
63
  axisLabel: { color: '#52606d', fontSize: REPORT_AXIS_LABEL_FONT_SIZE },
64
64
  },
65
65
  });
66
- //# sourceMappingURL=firestitch-report-echarts-D1UDj4L9.mjs.map
66
+ //# sourceMappingURL=firestitch-report-echarts-CUVOSBvJ.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"firestitch-report-echarts-D1UDj4L9.mjs","sources":["../../src/app/reports/theme/echarts.ts"],"sourcesContent":["// Tree-shaken ECharts build for the reports renderer: only the chart types and\n// components the option-builder emits are registered, keeping the lazy chunk\n// lean. Loaded on demand by ngx-echarts via provideEchartsCore — see\n// REPORT_ECHARTS_PROVIDERS in reports-echarts.provider.ts.\n//\n// The canvas renderer (not SVG) is deliberate: chart.getDataURL() needs it for\n// the high-res images the PPT/PDF exports will use.\n\nimport { BarChart, LineChart, PieChart } from 'echarts/charts';\nimport {\n DatasetComponent,\n GridComponent,\n LegendComponent,\n MarkAreaComponent,\n MarkLineComponent,\n TooltipComponent,\n} from 'echarts/components';\nimport * as echarts from 'echarts/core';\nimport { LabelLayout } from 'echarts/features';\nimport { CanvasRenderer } from 'echarts/renderers';\n\nimport { REPORT_CHART_COLORS_CSS } from './palette';\nimport { REPORT_AXIS_LABEL_FONT_SIZE, REPORT_CHART_FONT_FAMILY } from './typography';\n\necharts.use([\n BarChart,\n LineChart,\n PieChart,\n GridComponent,\n TooltipComponent,\n LegendComponent,\n DatasetComponent,\n // Reference lines and statistic lines (markLine), reference bands\n // (markArea), and the overlap hiding of 'auto' data labels (LabelLayout).\n MarkLineComponent,\n MarkAreaComponent,\n LabelLayout,\n CanvasRenderer,\n]);\n\n// The house theme — the single place report chart \"standards\" live (palette,\n// typography, tooltip styling). Polished defaults now; refined in the polish\n// phase.\necharts.registerTheme('report', {\n color: REPORT_CHART_COLORS_CSS,\n textStyle: {\n fontFamily: REPORT_CHART_FONT_FAMILY,\n },\n title: {\n textStyle: { color: '#1f2933', fontSize: 14, fontWeight: 600 },\n },\n legend: {\n textStyle: { color: '#52606d', fontSize: 11 },\n },\n tooltip: {\n backgroundColor: 'rgba(31, 41, 51, 0.92)',\n borderWidth: 0,\n textStyle: { color: '#fff', fontSize: 12 },\n },\n categoryAxis: {\n axisLine: { lineStyle: { color: '#cbd2d9' } },\n axisTick: { show: false },\n axisLabel: { color: '#52606d', fontSize: REPORT_AXIS_LABEL_FONT_SIZE },\n splitLine: { show: false },\n },\n valueAxis: {\n axisLine: { show: false },\n axisLabel: { color: '#52606d', fontSize: REPORT_AXIS_LABEL_FONT_SIZE },\n splitLine: { lineStyle: { color: '#e4e7eb' } },\n },\n timeAxis: {\n axisLine: { lineStyle: { color: '#cbd2d9' } },\n axisLabel: { color: '#52606d', fontSize: REPORT_AXIS_LABEL_FONT_SIZE },\n },\n});\n\nexport default echarts;\n"],"names":[],"mappings":";;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAkBA,OAAO,CAAC,GAAG,CAAC;IACV,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,aAAa;IACb,gBAAgB;IAChB,eAAe;IACf,gBAAgB;;;IAGhB,iBAAiB;IACjB,iBAAiB;IACjB,WAAW;IACX,cAAc;AACf,CAAA,CAAC,CAAC;AAEH;AACA;AACA;AACA,OAAO,CAAC,aAAa,CAAC,QAAQ,EAAE;AAC9B,IAAA,KAAK,EAAE,uBAAuB;AAC9B,IAAA,SAAS,EAAE;AACT,QAAA,UAAU,EAAE,wBAAwB;AACrC,KAAA;AACD,IAAA,KAAK,EAAE;AACL,QAAA,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;AAC/D,KAAA;AACD,IAAA,MAAM,EAAE;QACN,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE;AAC9C,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,eAAe,EAAE,wBAAwB;AACzC,QAAA,WAAW,EAAE,CAAC;QACd,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE;AAC3C,KAAA;AACD,IAAA,YAAY,EAAE;QACZ,QAAQ,EAAE,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;AAC7C,QAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;QACzB,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,2BAA2B,EAAE;AACtE,QAAA,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;AAC3B,KAAA;AACD,IAAA,SAAS,EAAE;AACT,QAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;QACzB,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,2BAA2B,EAAE;QACtE,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;AAC/C,KAAA;AACD,IAAA,QAAQ,EAAE;QACR,QAAQ,EAAE,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;QAC7C,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,2BAA2B,EAAE;AACvE,KAAA;AACF,CAAA,CAAC"}
1
+ {"version":3,"file":"firestitch-report-echarts-CUVOSBvJ.mjs","sources":["../../src/app/reports/theme/echarts.ts"],"sourcesContent":["// Tree-shaken ECharts build for the reports renderer: only the chart types and\n// components the option-builder emits are registered, keeping the lazy chunk\n// lean. Loaded on demand by ngx-echarts via provideEchartsCore — see\n// REPORT_ECHARTS_PROVIDERS in reports-echarts.provider.ts.\n//\n// The canvas renderer (not SVG) is deliberate: chart.getDataURL() needs it for\n// the high-res images the PPT/PDF exports will use.\n\nimport { BarChart, LineChart, PieChart } from 'echarts/charts';\nimport {\n DatasetComponent,\n GridComponent,\n LegendComponent,\n MarkAreaComponent,\n MarkLineComponent,\n TooltipComponent,\n} from 'echarts/components';\nimport * as echarts from 'echarts/core';\nimport { LabelLayout } from 'echarts/features';\nimport { CanvasRenderer } from 'echarts/renderers';\n\nimport { REPORT_CHART_COLORS_CSS } from './palette';\nimport { REPORT_AXIS_LABEL_FONT_SIZE, REPORT_CHART_FONT_FAMILY } from './typography';\n\necharts.use([\n BarChart,\n LineChart,\n PieChart,\n GridComponent,\n TooltipComponent,\n LegendComponent,\n DatasetComponent,\n // Reference lines and statistic lines (markLine), reference bands\n // (markArea), and the overlap hiding of 'auto' data labels (LabelLayout).\n MarkLineComponent,\n MarkAreaComponent,\n LabelLayout,\n CanvasRenderer,\n]);\n\n// The house theme — the single place report chart \"standards\" live (palette,\n// typography, tooltip styling). Polished defaults now; refined in the polish\n// phase.\necharts.registerTheme('report', {\n color: REPORT_CHART_COLORS_CSS,\n textStyle: {\n fontFamily: REPORT_CHART_FONT_FAMILY,\n },\n title: {\n textStyle: { color: '#1f2933', fontSize: 14, fontWeight: 600 },\n },\n legend: {\n textStyle: { color: '#52606d', fontSize: 11 },\n },\n tooltip: {\n backgroundColor: 'rgba(31, 41, 51, 0.92)',\n borderWidth: 0,\n textStyle: { color: '#fff', fontSize: 12 },\n },\n categoryAxis: {\n axisLine: { lineStyle: { color: '#cbd2d9' } },\n axisTick: { show: false },\n axisLabel: { color: '#52606d', fontSize: REPORT_AXIS_LABEL_FONT_SIZE },\n splitLine: { show: false },\n },\n valueAxis: {\n axisLine: { show: false },\n axisLabel: { color: '#52606d', fontSize: REPORT_AXIS_LABEL_FONT_SIZE },\n splitLine: { lineStyle: { color: '#e4e7eb' } },\n },\n timeAxis: {\n axisLine: { lineStyle: { color: '#cbd2d9' } },\n axisLabel: { color: '#52606d', fontSize: REPORT_AXIS_LABEL_FONT_SIZE },\n },\n});\n\nexport default echarts;\n"],"names":[],"mappings":";;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAkBA,OAAO,CAAC,GAAG,CAAC;IACV,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,aAAa;IACb,gBAAgB;IAChB,eAAe;IACf,gBAAgB;;;IAGhB,iBAAiB;IACjB,iBAAiB;IACjB,WAAW;IACX,cAAc;AACf,CAAA,CAAC,CAAC;AAEH;AACA;AACA;AACA,OAAO,CAAC,aAAa,CAAC,QAAQ,EAAE;AAC9B,IAAA,KAAK,EAAE,uBAAuB;AAC9B,IAAA,SAAS,EAAE;AACT,QAAA,UAAU,EAAE,wBAAwB;AACrC,KAAA;AACD,IAAA,KAAK,EAAE;AACL,QAAA,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;AAC/D,KAAA;AACD,IAAA,MAAM,EAAE;QACN,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE;AAC9C,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,eAAe,EAAE,wBAAwB;AACzC,QAAA,WAAW,EAAE,CAAC;QACd,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE;AAC3C,KAAA;AACD,IAAA,YAAY,EAAE;QACZ,QAAQ,EAAE,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;AAC7C,QAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;QACzB,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,2BAA2B,EAAE;AACtE,QAAA,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;AAC3B,KAAA;AACD,IAAA,SAAS,EAAE;AACT,QAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;QACzB,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,2BAA2B,EAAE;QACtE,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;AAC/C,KAAA;AACD,IAAA,QAAQ,EAAE;QACR,QAAQ,EAAE,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;QAC7C,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,2BAA2B,EAAE;AACvE,KAAA;AACF,CAAA,CAAC"}
@@ -7955,19 +7955,23 @@ class ReportFilterStateService {
7955
7955
  this._stateChanged$.next();
7956
7956
  }
7957
7957
  // Notifications relevant to ONE component: any change to a group one of its
7958
- // filters belongs to (or a full re-init).
7958
+ // filters belongs to (or a full re-init). The component is read on each
7959
+ // event, never captured at subscribe: a tile keeps its subscription across
7960
+ // report loads, so a filter added to it while the report is open (the MCP,
7961
+ // a chat edit, the settings dialog) reaches it on the first change of its
7962
+ // group.
7959
7963
  changesFor(component) {
7960
- const groupIds = new Set((component.filters ?? []).map((filter) => filter.filterGroupId));
7961
7964
  return this._changed$.asObservable()
7962
- .pipe(filter((groupId) => groupId === null || groupIds.has(groupId)));
7965
+ .pipe(filter((groupId) => groupId === null
7966
+ || (component().filters ?? []).some((filter) => filter.filterGroupId === groupId)));
7963
7967
  }
7964
7968
  // Notifications relevant to ANY of a set of components that comes and goes
7965
7969
  // (a page's values): a change to a group one of their filters belongs to.
7966
- // Unlike changesFor, the set is read on each event rather than captured at
7967
- // subscribe, so one subscription follows the set as the report reloads. A
7968
- // full re-init (null) is not passed on: init() runs on a report
7969
- // (re)load, and a caller of this checks on that reload itself whether the
7970
- // filter values its components are read with moved.
7970
+ // Like changesFor, the set is read on each event, so one subscription
7971
+ // follows the set as the report reloads. Unlike it, a full re-init (null)
7972
+ // is not passed on: init() runs on a report (re)load, and a caller of this
7973
+ // checks on that reload itself whether the filter values its components
7974
+ // are read with moved.
7971
7975
  changesForAny(components) {
7972
7976
  return this._changed$.asObservable()
7973
7977
  .pipe(filter((groupId) => groupId !== null
@@ -9958,6 +9962,18 @@ const NUMBER_UNITS = {
9958
9962
  };
9959
9963
  // What an abbreviated number's suffix multiplies it by, when a viewer types one.
9960
9964
  const UNIT_SUFFIXES = { k: 1e3, m: 1e6 };
9965
+ // A column written in thousands or millions is typed in them: the grid's
9966
+ // filter shows its K or M beside the inputs, so 12 there means 12,000. A K or
9967
+ // M typed after the digits wins, so 12K is still 12,000.
9968
+ const TYPED_UNITS = {
9969
+ [GridUnits.Thousands]: 1e3,
9970
+ [GridUnits.Millions]: 1e6,
9971
+ };
9972
+ // The significant digits a scaled typed number keeps, as fs-grid's own reader
9973
+ // keeps them: scaling leaves binary noise behind (12.3% reads
9974
+ // 0.12300000000000001, 1.005K 1004.9999999999999), which would move a range's
9975
+ // end past the very value it names.
9976
+ const TYPED_DIGITS = 15;
9961
9977
  const YES = 'Yes';
9962
9978
  const NO = 'No';
9963
9979
  // A grid tile's formatter: every value written as the report writes it on
@@ -10035,20 +10051,25 @@ function optionLabel(value, field) {
10035
10051
  return field.options?.find((option) => option.key === value)?.label ?? null;
10036
10052
  }
10037
10053
  // A number a viewer typed, read in the report's locale: its grouping dropped,
10038
- // its decimal mark read, a K or M suffix multiplied out, and a percent of a
10039
- // ratio-percent report read back to its ratio. null when it is no number.
10054
+ // its decimal mark read, a K or M suffix multiplied out (else the column's own
10055
+ // thousands or millions), and a percent of a ratio-percent report read back to
10056
+ // its ratio. The placement's units and format win over the field's, as they do
10057
+ // where the number is written (numberSpec). null when it is no number.
10040
10058
  function readGridNumber(context, text, field, placement) {
10041
10059
  const decimal = decimalMark(context.format.locale);
10042
10060
  const trimmed = text.trim().toLowerCase();
10043
- const multiplier = UNIT_SUFFIXES[trimmed.slice(-1)] ?? 1;
10061
+ const multiplier = UNIT_SUFFIXES[trimmed.slice(-1)]
10062
+ ?? TYPED_UNITS[placement?.units ?? field.units ?? GridUnits.None]
10063
+ ?? 1;
10044
10064
  const digits = trimmed
10045
10065
  .split(decimal).map((part) => part.replace(/[^0-9-]/g, '')).join('.');
10046
10066
  const number = digits && digits !== '-' && digits !== '.' ? Number(digits) * multiplier : NaN;
10047
10067
  if (!Number.isFinite(number)) {
10048
10068
  return null;
10049
10069
  }
10050
- const isPercent = (placement?.format ?? field.format) === GridFormat.Percent;
10051
- return isPercent && context.format.ratioPercent ? number / 100 : number;
10070
+ const isRatio = (placement?.format ?? field.format) === GridFormat.Percent && context.format.ratioPercent;
10071
+ const value = isRatio ? number / 100 : number;
10072
+ return multiplier === 1 && !isRatio ? value : Number(value.toPrecision(TYPED_DIGITS));
10052
10073
  }
10053
10074
  function decimalMark(locale) {
10054
10075
  return numberFormat(locale, {}).formatToParts(1.5).find((part) => part.type === 'decimal')?.value ?? '.';
@@ -10065,9 +10086,14 @@ function decimalMark(locale) {
10065
10086
  // re-init the filter state) reads the table again at once, even when its
10066
10087
  // filters did not move: the rows behind it, or its own query, may have.
10067
10088
  // The load is counted into the params (reportLoads), since fs-grid reads
10068
- // only when they move. The new params land in the same tick as the
10069
- // reloaded tile, so a load that also moves the authored layout costs one
10070
- // read.
10089
+ // only when they move. The params are derived from the tile as it is
10090
+ // drawn (params), so the read of a load carries the filters the load
10091
+ // brought, a filter added since the report opened included, and lands in
10092
+ // the same tick as the reloaded tile: a load that also moves the authored
10093
+ // layout costs one read.
10094
+ // - Which filter groups and whether the group-by reach the tile is read
10095
+ // from the tile as it stands at each change, never as it was drawn: a
10096
+ // filter added while the report is open reads on its first pick.
10071
10097
  // - The layout on screen is the state service's (gridLayout): the viewer's
10072
10098
  // own while they changed it from the layout authored now, else the
10073
10099
  // authored one. It outlives a page switch, and the chat reads it. So do
@@ -10091,7 +10117,16 @@ function decimalMark(locale) {
10091
10117
  class ComponentGridComponent {
10092
10118
  reportId = input.required();
10093
10119
  component = input.required();
10094
- params = signal(null);
10120
+ // What fs-grid reads with: the tile's params (_readParams) for the
10121
+ // component drawn now, read again at each settled change (_paramsRead).
10122
+ // A load re-inits the filter state a tick before the tile gets the
10123
+ // component it brought, so deriving them here is what puts a filter added
10124
+ // by that load into its read.
10125
+ params = computed(() => {
10126
+ const component = this.component();
10127
+ this._paramsRead();
10128
+ return untracked(() => this._readParams(component));
10129
+ });
10095
10130
  controls = computed(() => this.component().config?.controls ?? {});
10096
10131
  source = computed(() => new ReportGridSource(this._reportData, this.reportId(), this._componentId(), {
10097
10132
  locale: () => this._filterState.formatContext().locale,
@@ -10118,6 +10153,8 @@ class ComponentGridComponent {
10118
10153
  _paramsVersion = 0;
10119
10154
  // The report loads seen since the tile was drawn (ReportGridParams).
10120
10155
  _reportLoads = 0;
10156
+ // Moves each time a settled change has the params read again.
10157
+ _paramsRead = signal(0);
10121
10158
  ngOnInit() {
10122
10159
  this.expansion.set(this._filterState.gridExpansion(this.component()));
10123
10160
  this._init();
@@ -10131,16 +10168,14 @@ class ComponentGridComponent {
10131
10168
  }
10132
10169
  _init() {
10133
10170
  this._paramsVersion = this._filterState.stateVersion();
10134
- this.params.set(this._readParams());
10135
10171
  // A null group is a full re-init: a report load, or a reset.
10136
- this._filterState.changesFor(this.component())
10172
+ this._filterState.changesFor(this.component)
10137
10173
  .pipe(switchMap$1((groupId) => this._settled$(groupId === null)), tap$1((isLoad) => this._applyParams(isLoad)), takeUntilDestroyed(this._destroyRef))
10138
10174
  .subscribe();
10139
- if (followsGroupBy(this.component())) {
10140
- this._filterState.frequencyChanges()
10141
- .pipe(takeUntilDestroyed(this._destroyRef))
10142
- .subscribe(() => this._applyParams());
10143
- }
10175
+ // A load can make the table follow the group-by, or stop.
10176
+ this._filterState.frequencyChanges()
10177
+ .pipe(filter$1(() => followsGroupBy(this.component())), tap$1(() => this._applyParams()), takeUntilDestroyed(this._destroyRef))
10178
+ .subscribe();
10144
10179
  }
10145
10180
  // Once the tile's first read is in, its notes prune the viewer's layout
10146
10181
  // (prunedGridLayout). A tile showing its authored layout has nothing to
@@ -10181,12 +10216,11 @@ class ComponentGridComponent {
10181
10216
  this._reportLoads++;
10182
10217
  }
10183
10218
  this._paramsVersion = this._filterState.stateVersion();
10184
- this.params.set(this._readParams());
10219
+ this._paramsRead.update((count) => count + 1);
10185
10220
  }
10186
10221
  // The tile's report filters, 'any' groups and group-by grain as they stand
10187
10222
  // (tileState()), and the report loads it has seen.
10188
- _readParams() {
10189
- const component = this.component();
10223
+ _readParams(component) {
10190
10224
  const { matchAnyGroupIds, frequency } = this._filterState.tileState(component);
10191
10225
  return {
10192
10226
  filters: this._filterState.resolveForComponent(component),
@@ -10196,7 +10230,7 @@ class ComponentGridComponent {
10196
10230
  };
10197
10231
  }
10198
10232
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10199
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ComponentGridComponent, isStandalone: true, selector: "app-report-component-grid", inputs: { reportId: { classPropertyName: "reportId", publicName: "reportId", isSignal: true, isRequired: true, transformFunction: null }, component: { classPropertyName: "component", publicName: "component", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<!-- The comparison and window note strip above the table, from the report\n line of the latest read; the windows' descriptions and notes go in each\n column's \"About this column\" instead. -->\n<div class=\"fs-column grid-pane\">\n <app-report-component-note\n [data]=\"noteData()\"\n [windowNotes]=\"false\">\n </app-report-component-note>\n <!-- The params are seeded before the first render, so the grid's first read\n carries the tile's report filters. -->\n @if (params(); as params) {\n <fs-grid\n class=\"fs-flex\"\n [source]=\"source()\"\n [layout]=\"layout()\"\n [expansion]=\"expansion()\"\n [clearedFilter]=\"clearedFilter()\"\n (expansionChange)=\"setExpansion($event)\"\n [params]=\"params\"\n [controls]=\"controls()\"\n [formatter]=\"formatter()\"\n (layoutChange)=\"setLayout($event)\">\n <ng-template fsGridCell let-value let-field=\"field\" let-record=\"record\" let-text=\"text\">\n @if (value | fsReportGridCell: field : record : text : noteData() : formatContext(); as shown) {\n <!-- A cell that says why (a missing or zero prior) opens it on a\n click; no cell carries a native tooltip. -->\n @if (shown.title) {\n <span\n class=\"explained\"\n fsPopover\n trigger=\"click\"\n [template]=\"explanation\"\n [indication]=\"false\"\n [maxWidth]=\"320\">\n {{ shown.text }}\n </span>\n <ng-template #explanation>{{ shown.title }}</ng-template>\n } @else {\n <span>{{ shown.text }}</span>\n }\n }\n </ng-template>\n </fs-grid>\n }\n</div>\n", styles: [":host{display:block;height:100%}.grid-pane{height:100%;min-height:0}fs-grid{min-height:0}.explained{cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px}\n"], dependencies: [{ kind: "component", type: FsGridComponent, selector: "fs-grid", inputs: ["source", "layout", "controls", "params", "expansion", "panel", "editorOpen", "formatter", "clearedFilter"], outputs: ["layoutChange", "expansionChange", "panelChange", "editorOpenChange"] }, { kind: "directive", type: FsGridCellDirective, selector: "[fsGridCell]" }, { kind: "directive", type: FsPopoverDirective, selector: "[fsPopover]", inputs: ["text", "template", "data", "leaveDelay", "showDelay", "maxWidth", "maxHeight", "wrapperClass", "autoShow", "autoClose", "loadingDiameter", "loading", "indication", "position", "theme", "size", "trigger", "enabled", "panel"], outputs: ["closed"], exportAs: ["fsPopover"] }, { kind: "component", type: ComponentNoteComponent, selector: "app-report-component-note", inputs: ["data", "windows", "windowDescriptions", "windowNotes"] }, { kind: "pipe", type: ReportGridCellPipe, name: "fsReportGridCell" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10233
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ComponentGridComponent, isStandalone: true, selector: "app-report-component-grid", inputs: { reportId: { classPropertyName: "reportId", publicName: "reportId", isSignal: true, isRequired: true, transformFunction: null }, component: { classPropertyName: "component", publicName: "component", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<!-- The comparison and window note strip above the table, from the report\n line of the latest read; the windows' descriptions and notes go in each\n column's \"About this column\" instead. -->\n<div class=\"fs-column grid-pane\">\n <app-report-component-note\n [data]=\"noteData()\"\n [windowNotes]=\"false\">\n </app-report-component-note>\n <!-- The params are read from the tile drawn (params), from its first\n render on, so every read carries the tile's report filters. -->\n <fs-grid\n class=\"fs-flex\"\n [source]=\"source()\"\n [layout]=\"layout()\"\n [expansion]=\"expansion()\"\n [clearedFilter]=\"clearedFilter()\"\n (expansionChange)=\"setExpansion($event)\"\n [params]=\"params()\"\n [controls]=\"controls()\"\n [formatter]=\"formatter()\"\n (layoutChange)=\"setLayout($event)\">\n <ng-template fsGridCell let-value let-field=\"field\" let-record=\"record\" let-text=\"text\">\n @if (value | fsReportGridCell: field : record : text : noteData() : formatContext(); as shown) {\n <!-- A cell that says why (a missing or zero prior) opens it on a\n click; no cell carries a native tooltip. -->\n @if (shown.title) {\n <span\n class=\"explained\"\n fsPopover\n trigger=\"click\"\n [template]=\"explanation\"\n [indication]=\"false\"\n [maxWidth]=\"320\">\n {{ shown.text }}\n </span>\n <ng-template #explanation>{{ shown.title }}</ng-template>\n } @else {\n <span>{{ shown.text }}</span>\n }\n }\n </ng-template>\n </fs-grid>\n</div>\n", styles: [":host{display:block;height:100%}.grid-pane{height:100%;min-height:0}fs-grid{min-height:0}.explained{cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px}\n"], dependencies: [{ kind: "component", type: FsGridComponent, selector: "fs-grid", inputs: ["source", "layout", "controls", "params", "expansion", "panel", "editorOpen", "formatter", "clearedFilter"], outputs: ["layoutChange", "expansionChange", "panelChange", "editorOpenChange"] }, { kind: "directive", type: FsGridCellDirective, selector: "[fsGridCell]" }, { kind: "directive", type: FsPopoverDirective, selector: "[fsPopover]", inputs: ["text", "template", "data", "leaveDelay", "showDelay", "maxWidth", "maxHeight", "wrapperClass", "autoShow", "autoClose", "loadingDiameter", "loading", "indication", "position", "theme", "size", "trigger", "enabled", "panel"], outputs: ["closed"], exportAs: ["fsPopover"] }, { kind: "component", type: ComponentNoteComponent, selector: "app-report-component-note", inputs: ["data", "windows", "windowDescriptions", "windowNotes"] }, { kind: "pipe", type: ReportGridCellPipe, name: "fsReportGridCell" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10200
10234
  }
10201
10235
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentGridComponent, decorators: [{
10202
10236
  type: Component,
@@ -10206,7 +10240,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
10206
10240
  FsPopoverDirective,
10207
10241
  ComponentNoteComponent,
10208
10242
  ReportGridCellPipe,
10209
- ], template: "<!-- The comparison and window note strip above the table, from the report\n line of the latest read; the windows' descriptions and notes go in each\n column's \"About this column\" instead. -->\n<div class=\"fs-column grid-pane\">\n <app-report-component-note\n [data]=\"noteData()\"\n [windowNotes]=\"false\">\n </app-report-component-note>\n <!-- The params are seeded before the first render, so the grid's first read\n carries the tile's report filters. -->\n @if (params(); as params) {\n <fs-grid\n class=\"fs-flex\"\n [source]=\"source()\"\n [layout]=\"layout()\"\n [expansion]=\"expansion()\"\n [clearedFilter]=\"clearedFilter()\"\n (expansionChange)=\"setExpansion($event)\"\n [params]=\"params\"\n [controls]=\"controls()\"\n [formatter]=\"formatter()\"\n (layoutChange)=\"setLayout($event)\">\n <ng-template fsGridCell let-value let-field=\"field\" let-record=\"record\" let-text=\"text\">\n @if (value | fsReportGridCell: field : record : text : noteData() : formatContext(); as shown) {\n <!-- A cell that says why (a missing or zero prior) opens it on a\n click; no cell carries a native tooltip. -->\n @if (shown.title) {\n <span\n class=\"explained\"\n fsPopover\n trigger=\"click\"\n [template]=\"explanation\"\n [indication]=\"false\"\n [maxWidth]=\"320\">\n {{ shown.text }}\n </span>\n <ng-template #explanation>{{ shown.title }}</ng-template>\n } @else {\n <span>{{ shown.text }}</span>\n }\n }\n </ng-template>\n </fs-grid>\n }\n</div>\n", styles: [":host{display:block;height:100%}.grid-pane{height:100%;min-height:0}fs-grid{min-height:0}.explained{cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px}\n"] }]
10243
+ ], template: "<!-- The comparison and window note strip above the table, from the report\n line of the latest read; the windows' descriptions and notes go in each\n column's \"About this column\" instead. -->\n<div class=\"fs-column grid-pane\">\n <app-report-component-note\n [data]=\"noteData()\"\n [windowNotes]=\"false\">\n </app-report-component-note>\n <!-- The params are read from the tile drawn (params), from its first\n render on, so every read carries the tile's report filters. -->\n <fs-grid\n class=\"fs-flex\"\n [source]=\"source()\"\n [layout]=\"layout()\"\n [expansion]=\"expansion()\"\n [clearedFilter]=\"clearedFilter()\"\n (expansionChange)=\"setExpansion($event)\"\n [params]=\"params()\"\n [controls]=\"controls()\"\n [formatter]=\"formatter()\"\n (layoutChange)=\"setLayout($event)\">\n <ng-template fsGridCell let-value let-field=\"field\" let-record=\"record\" let-text=\"text\">\n @if (value | fsReportGridCell: field : record : text : noteData() : formatContext(); as shown) {\n <!-- A cell that says why (a missing or zero prior) opens it on a\n click; no cell carries a native tooltip. -->\n @if (shown.title) {\n <span\n class=\"explained\"\n fsPopover\n trigger=\"click\"\n [template]=\"explanation\"\n [indication]=\"false\"\n [maxWidth]=\"320\">\n {{ shown.text }}\n </span>\n <ng-template #explanation>{{ shown.title }}</ng-template>\n } @else {\n <span>{{ shown.text }}</span>\n }\n }\n </ng-template>\n </fs-grid>\n</div>\n", styles: [":host{display:block;height:100%}.grid-pane{height:100%;min-height:0}fs-grid{min-height:0}.explained{cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px}\n"] }]
10210
10244
  }] });
10211
10245
 
10212
10246
  // A KPI's trend over its last periods: a line that stretches to the card's
@@ -10469,9 +10503,10 @@ class ComponentListComponent {
10469
10503
  this._initListConfig(config);
10470
10504
  this._observePinOffsets();
10471
10505
  // A change to any group this list's filters belong to (from the report bar
10472
- // or the component header strip) re-runs the list.
10473
- // So does a change of the text in its search box (in the component header).
10474
- merge(this._filterState.changesFor(component), this._filterState.listSearchChanges(component.id))
10506
+ // or the component header strip) re-runs the list, a filter added since
10507
+ // the list was drawn included. So does a change of the text in its search
10508
+ // box (in the component header).
10509
+ merge(this._filterState.changesFor(this.component), this._filterState.listSearchChanges(component.id))
10475
10510
  .pipe(tap$1(() => this.listComponent()?.reload()), takeUntilDestroyed(this._destroyRef))
10476
10511
  .subscribe();
10477
10512
  }
@@ -11922,19 +11957,21 @@ class ReportComponentComponent {
11922
11957
  tileLoads(this._refresh$, () => this._load(), () => this._filterState.stateVersion())
11923
11958
  .pipe(tap$1((result) => this._loaded(result)), takeUntilDestroyed(this._destroyRef))
11924
11959
  .subscribe();
11925
- this._filterState.changesFor(this.component)
11926
- .pipe(takeUntilDestroyed(this._destroyRef))
11927
- .subscribe(() => this._refresh$.next());
11960
+ // A change to one of the tile's filter groups, read from the tile as
11961
+ // it stands, so a filter a report load added reads on its first pick.
11962
+ this._filterState.changesFor(() => this.component)
11963
+ .pipe(tap$1(() => this._refresh$.next()), takeUntilDestroyed(this._destroyRef))
11964
+ .subscribe();
11928
11965
  // A group-by change re-buckets every tile that follows it; it isn't
11929
- // scoped to a filter group, so it rides its own stream.
11930
- if (followsGroupBy(this.component)) {
11931
- this._filterState.frequencyChanges()
11932
- .pipe(takeUntilDestroyed(this._destroyRef))
11933
- .subscribe(() => {
11934
- this._grainMoved = true;
11935
- this._refresh$.next();
11936
- });
11937
- }
11966
+ // scoped to a filter group, so it rides its own stream. Whether the
11967
+ // tile follows it is read at each change: a load can make it follow
11968
+ // (a chart moved onto a time axis) or stop.
11969
+ this._filterState.frequencyChanges()
11970
+ .pipe(filter$1(() => followsGroupBy(this.component)), tap$1(() => {
11971
+ this._grainMoved = true;
11972
+ this._refresh$.next();
11973
+ }), takeUntilDestroyed(this._destroyRef))
11974
+ .subscribe();
11938
11975
  // A drill moves this chart alone.
11939
11976
  this._filterState.drillChanges(this.component.id)
11940
11977
  .pipe(takeUntilDestroyed(this._destroyRef))
@@ -12351,7 +12388,7 @@ class ReportComponentComponent {
12351
12388
  // A change to one group the header shows with another value (another bar
12352
12389
  // set it) builds it again too.
12353
12390
  _rebuildHeaderOnNewState() {
12354
- this._filterState.changesFor(this.component)
12391
+ this._filterState.changesFor(() => this.component)
12355
12392
  .pipe(filter$1((groupId) => (groupId === null
12356
12393
  ? this._filterState.stateVersion() !== this._headerVersion()
12357
12394
  : this._showsOtherValue(groupId))), tap$1(() => this._buildFilterConfig()), takeUntilDestroyed(this._destroyRef))
@@ -15819,7 +15856,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
15819
15856
  // in the report's format context, as on screen.
15820
15857
  async function renderChartImage(component, data, width, height, context, pixelRatio = 3) {
15821
15858
  // The same tree-shaken echarts build + 'report' theme the live canvas uses.
15822
- const echarts = (await import('./firestitch-report-echarts-D1UDj4L9.mjs')).default;
15859
+ const echarts = (await import('./firestitch-report-echarts-CUVOSBvJ.mjs')).default;
15823
15860
  const host = document.createElement('div');
15824
15861
  host.style.width = `${width}px`;
15825
15862
  host.style.height = `${height}px`;
@@ -19512,7 +19549,7 @@ class FsAiReportComponent {
19512
19549
  ReportAiChatData,
19513
19550
  { provide: FsAiChatData, useExisting: ReportAiChatData },
19514
19551
  // Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
19515
- provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-D1UDj4L9.mjs').then((module) => module.default) }),
19552
+ provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CUVOSBvJ.mjs').then((module) => module.default) }),
19516
19553
  // The bar's date pickers start their weeks on Monday and list the built-in
19517
19554
  // presets; a preset only sets the days.
19518
19555
  provideReportDatePicker(),
@@ -19536,7 +19573,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
19536
19573
  ReportAiChatData,
19537
19574
  { provide: FsAiChatData, useExisting: ReportAiChatData },
19538
19575
  // Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
19539
- provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-D1UDj4L9.mjs').then((module) => module.default) }),
19576
+ provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CUVOSBvJ.mjs').then((module) => module.default) }),
19540
19577
  // The bar's date pickers start their weeks on Monday and list the built-in
19541
19578
  // presets; a preset only sets the days.
19542
19579
  provideReportDatePicker(),
@@ -19629,4 +19666,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
19629
19666
  */
19630
19667
 
19631
19668
  export { FsAiReportsComponent as F, GRANULARITY_OPTIONS as G, REPORT_CHART_COLORS_CSS as R, REPORT_CHART_FONT_FAMILY as a, REPORT_AXIS_LABEL_FONT_SIZE as b, FsAiReportComponent as c, ReportData as d, ReportService as e, ReportFilterStateService as f, ReportNodeType as g, ReportState as h, ReportValueFormat as i, ReportValueFormula as j, ReportListColumnFormat as k, ReportNumberUnits as l, ReportChartDataLabels as m, ReportChartColor as n, ReportChartLineStyle as o, ReportChartMarkStat as p, ReportChartDateLabel as q, ReportChartSpanThrough as r, ReportChangePolarity as s };
19632
- //# sourceMappingURL=firestitch-report-firestitch-report-cys9XusN.mjs.map
19669
+ //# sourceMappingURL=firestitch-report-firestitch-report-CfvBTMrG.mjs.map