@firestitch/report 18.21.3 → 18.21.4

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.
@@ -11771,7 +11771,7 @@ class ComponentTextComponent {
11771
11771
  }
11772
11772
  }
11773
11773
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentTextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11774
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ComponentTextComponent, isStandalone: true, selector: "app-report-component-text", inputs: { html: { classPropertyName: "html", publicName: "html", isSignal: true, isRequired: false, transformFunction: null }, tokens: { classPropertyName: "tokens", publicName: "tokens", isSignal: true, isRequired: true, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null }, editMode: { classPropertyName: "editMode", publicName: "editMode", isSignal: true, isRequired: false, transformFunction: null }, shownElsewhere: { classPropertyName: "shownElsewhere", publicName: "shownElsewhere", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", editEnded: "editEnded", valueSettings: "valueSettings" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "style.--text-size": "typeScale.body", "style.--text-h1-size": "typeScale.h1", "style.--text-h2-size": "typeScale.h2", "style.--text-h3-size": "typeScale.h3", "style.--text-color": "typeScale.color", "style.--text-table-border": "tableLook.border", "style.--text-table-padding": "tableLook.padding", "style.--text-table-margin": "tableLook.margin" } }, viewQueries: [{ propertyName: "_editor", first: true, predicate: FsHtmlEditorComponent, descendants: true, isSignal: true }, { propertyName: "_valuePopoverAnchor", first: true, predicate: ["valuePopoverAnchor"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "@if (editing()) {\n <!-- Floating fs-html-editor: no frame, Froala's inline toolbar pops up over\n the text. pointerdown stays here so a press in the text never starts a\n drag of the block or a pan of the canvas. -->\n <fs-html-editor\n class=\"text-content editing\"\n [config]=\"editorConfig\"\n [(ngModel)]=\"draft\"\n (pointerdown)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n </fs-html-editor>\n\n <!-- One notice at a time: an edit stopped because it would have removed\n values, an edit that left values no text reads, or a refused command.\n mousedown keeps the editor's focus and selection, so Undo reaches it. -->\n @if (notice(); as current) {\n <div\n class=\"value-notice fs-row.gap-sm\"\n (pointerdown)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.preventDefault()\">\n @switch (current.kind) {\n @case ('blocked') {\n @if (current.block.removeSelection) {\n <span>\n This selection includes {{ current.block.count }} {{ current.block.count === 1 ? 'value' : 'values' }}.\n </span>\n <button\n mat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"removeBlockedSelection()\">\n Remove text and {{ current.block.count === 1 ? 'value' : current.block.count + ' values' }}\n </button>\n } @else {\n <span>To take a value out, select it, then press Delete.</span>\n }\n }\n @case ('unused') {\n <span>{{ current.count }} {{ current.count === 1 ? 'value' : 'values' }} moved to Not used</span>\n <button\n mat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"undoValuesUnused()\">\n Undo\n </button>\n }\n @case ('message') {\n <span>{{ current.text }}</span>\n }\n }\n <button\n mat-button\n type=\"button\"\n (click)=\"dismissNotice()\">\n Dismiss\n </button>\n </div>\n }\n} @else if (editMode() && isBlank()) {\n <!-- An empty block while the canvas is laid out: a hint in the block's own\n type, never its html, so nothing is typed onto it or exported. -->\n <div class=\"text-content text-hint\">Double-click to edit</div>\n} @else {\n <div\n class=\"text-content\"\n [innerHTML]=\"displayHtml()\">\n </div>\n}\n\n<!-- A clicked value chip's popover: an fs-popover on an invisible anchor laid\n over the chip (the chip itself belongs to the editor). Outside the editing\n block, so a popover still open when editing ends closes the usual way. -->\n<span\n #valuePopoverAnchor\n class=\"value-popover-anchor\"\n aria-hidden=\"true\"\n fsPopover\n trigger=\"click\"\n wrapperClass=\"value-popover-wrapper\"\n [template]=\"valuePopoverTemplate\"\n [indication]=\"false\"\n [maxWidth]=\"320\"\n [style.left.px]=\"valuePopover()?.left\"\n [style.top.px]=\"valuePopover()?.top\"\n [style.width.px]=\"valuePopover()?.width\"\n [style.height.px]=\"valuePopover()?.height\">\n</span>\n<ng-template\n #valuePopoverTemplate\n let-popover=\"popover\">\n <!-- What the value is, and its settings (where it can also be deleted); a\n chip naming no value on the page has no settings. -->\n <div class=\"value-popover fs-row\">\n <div class=\"fs-flex\">\n <div class=\"value-popover-name\">{{ valuePopoverValue()?.name ?? valuePopover()?.key }}</div>\n @if (!valuePopoverValue()) {\n <div class=\"small\">Not a value on this page</div>\n }\n </div>\n @if (valuePopoverValue()) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"value-popover-settings\"\n matTooltip=\"Settings\"\n (click)=\"openValueSettings(popover)\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;height:100%}.text-content{display:block;width:100%;height:100%;overflow:hidden;font-family:Arial,Helvetica,sans-serif;font-size:var(--text-size);line-height:1.25;color:var(--text-color);word-wrap:break-word;letter-spacing:normal;word-spacing:normal;font-weight:400;font-style:normal;text-transform:none;white-space:normal}.text-content ::ng-deep p,.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3,.text-content ::ng-deep ul,.text-content ::ng-deep ol{margin:0}.text-content ::ng-deep h1{font-size:var(--text-h1-size)}.text-content ::ng-deep h2{font-size:var(--text-h2-size)}.text-content ::ng-deep h3{font-size:var(--text-h3-size)}.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3{font-weight:700;line-height:1.2}.text-content ::ng-deep ul,.text-content ::ng-deep ol{padding-left:1.4em}.text-content ::ng-deep p:empty:before{content:\"\\200b\"}.text-content ::ng-deep a{color:inherit}.text-content ::ng-deep table{border-collapse:collapse;margin:var(--text-table-margin) 0}.text-content ::ng-deep th,.text-content ::ng-deep td{border:var(--text-table-border);padding:var(--text-table-padding);vertical-align:middle;text-align:left}.text-content ::ng-deep th{text-align:center;font-weight:700}.text-content.editing{overflow:auto;cursor:text}.text-content.editing ::ng-deep [data-embed-key],.text-content.editing ::ng-deep [data-embed-field]{padding:0 2px;border-radius:3px;background-color:#4e79a729;-webkit-user-select:all;user-select:all}.text-content.editing ::ng-deep [data-embed-key]{cursor:pointer}.text-content.editing ::ng-deep [data-embed-key]:hover{background-color:#4e79a747}.text-content.editing ::ng-deep [data-embed-missing]{background-color:#e1575924;outline:1px dashed rgba(225,87,89,.7)}.text-content.editing ::ng-deep th,.text-content.editing ::ng-deep tfoot td{background-color:transparent}.text-content.editing ::ng-deep .fr-table-selector{display:none}.text-content.editing ::ng-deep .fr-table-selection-hover{outline:none}.text-hint{color:#9aa5b1;-webkit-user-select:none;user-select:none}.value-popover-anchor{position:absolute;pointer-events:none}::ng-deep .value-popover-wrapper .popover-content{padding:15px!important}.value-popover{gap:15px}.value-popover-name{font-weight:600}.value-popover-settings{--mdc-icon-button-state-layer-size: 24px;width:24px;height:24px;padding:0}.value-notice{position:absolute;top:100%;left:0;z-index:2;margin-top:4px;padding:2px 4px 2px 10px;border:1px solid #d9dee3;border-radius:4px;background:#fff;white-space:nowrap;font-family:Arial,Helvetica,sans-serif}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FsHtmlEditorComponent, selector: "fs-html-editor", inputs: ["config", "disabled"] }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11774
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ComponentTextComponent, isStandalone: true, selector: "app-report-component-text", inputs: { html: { classPropertyName: "html", publicName: "html", isSignal: true, isRequired: false, transformFunction: null }, tokens: { classPropertyName: "tokens", publicName: "tokens", isSignal: true, isRequired: true, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null }, editMode: { classPropertyName: "editMode", publicName: "editMode", isSignal: true, isRequired: false, transformFunction: null }, shownElsewhere: { classPropertyName: "shownElsewhere", publicName: "shownElsewhere", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", editEnded: "editEnded", valueSettings: "valueSettings" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "style.--text-size": "typeScale.body", "style.--text-h1-size": "typeScale.h1", "style.--text-h2-size": "typeScale.h2", "style.--text-h3-size": "typeScale.h3", "style.--text-color": "typeScale.color", "style.--text-table-border": "tableLook.border", "style.--text-table-padding": "tableLook.padding", "style.--text-table-margin": "tableLook.margin" } }, viewQueries: [{ propertyName: "_editor", first: true, predicate: FsHtmlEditorComponent, descendants: true, isSignal: true }, { propertyName: "_valuePopoverAnchor", first: true, predicate: ["valuePopoverAnchor"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "@if (editing()) {\n <!-- Floating fs-html-editor: no frame, Froala's inline toolbar pops up over\n the text. pointerdown stays here so a press in the text never starts a\n drag of the block or a pan of the canvas. -->\n <fs-html-editor\n class=\"text-content editing\"\n [config]=\"editorConfig\"\n [(ngModel)]=\"draft\"\n (pointerdown)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n </fs-html-editor>\n\n <!-- One notice at a time: an edit stopped because it would have removed\n values, an edit that left values no text reads, or a refused command.\n mousedown keeps the editor's focus and selection, so Undo reaches it. -->\n @if (notice(); as current) {\n <div\n class=\"value-notice fs-row.gap-sm\"\n (pointerdown)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.preventDefault()\">\n @switch (current.kind) {\n @case ('blocked') {\n @if (current.block.removeSelection) {\n <span>\n This selection includes {{ current.block.count }} {{ current.block.count === 1 ? 'value' : 'values' }}.\n </span>\n <button\n mat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"removeBlockedSelection()\">\n Remove text and {{ current.block.count === 1 ? 'value' : current.block.count + ' values' }}\n </button>\n } @else {\n <span>To take a value out, select it, then press Delete.</span>\n }\n }\n @case ('unused') {\n <span>{{ current.count }} {{ current.count === 1 ? 'value' : 'values' }} moved to Not used</span>\n <button\n mat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"undoValuesUnused()\">\n Undo\n </button>\n }\n @case ('message') {\n <span>{{ current.text }}</span>\n }\n }\n <button\n mat-button\n type=\"button\"\n (click)=\"dismissNotice()\">\n Dismiss\n </button>\n </div>\n }\n} @else if (editMode() && isBlank()) {\n <!-- An empty block while the canvas is laid out: a hint in the block's own\n type, never its html, so nothing is typed onto it or exported. -->\n <div class=\"text-content text-hint\">Double-click to edit</div>\n} @else {\n <div\n class=\"text-content\"\n [innerHTML]=\"displayHtml()\">\n </div>\n}\n\n<!-- A clicked value chip's popover: an fs-popover on an invisible anchor laid\n over the chip (the chip itself belongs to the editor). Outside the editing\n block, so a popover still open when editing ends closes the usual way. -->\n<span\n #valuePopoverAnchor\n class=\"value-popover-anchor\"\n aria-hidden=\"true\"\n fsPopover\n trigger=\"click\"\n wrapperClass=\"value-popover-wrapper\"\n [template]=\"valuePopoverTemplate\"\n [indication]=\"false\"\n [maxWidth]=\"320\"\n [style.left.px]=\"valuePopover()?.left\"\n [style.top.px]=\"valuePopover()?.top\"\n [style.width.px]=\"valuePopover()?.width\"\n [style.height.px]=\"valuePopover()?.height\">\n</span>\n<ng-template\n #valuePopoverTemplate\n let-popover=\"popover\">\n <!-- What the value is, and its settings (where it can also be deleted); a\n chip naming no value on the page has no settings. -->\n <div class=\"value-popover fs-row\">\n <div class=\"fs-flex\">\n <div class=\"value-popover-name\">{{ valuePopoverValue()?.name ?? valuePopover()?.key }}</div>\n @if (!valuePopoverValue()) {\n <div class=\"small\">Not a value on this page</div>\n }\n </div>\n @if (valuePopoverValue()) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"value-popover-settings\"\n matTooltip=\"Settings\"\n (click)=\"openValueSettings(popover)\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;height:100%}.text-content{display:block;width:100%;height:100%;overflow:visible;font-family:Arial,Helvetica,sans-serif;font-size:var(--text-size);line-height:1.25;color:var(--text-color);word-wrap:break-word;letter-spacing:normal;word-spacing:normal;font-weight:400;font-style:normal;text-transform:none;white-space:normal}.text-content ::ng-deep p,.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3,.text-content ::ng-deep ul,.text-content ::ng-deep ol{margin:0}.text-content ::ng-deep h1{font-size:var(--text-h1-size)}.text-content ::ng-deep h2{font-size:var(--text-h2-size)}.text-content ::ng-deep h3{font-size:var(--text-h3-size)}.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3{font-weight:700;line-height:1.2}.text-content ::ng-deep ul,.text-content ::ng-deep ol{padding-left:1.4em}.text-content ::ng-deep p:empty:before{content:\"\\200b\"}.text-content ::ng-deep a{color:inherit}.text-content ::ng-deep table{border-collapse:collapse;margin:var(--text-table-margin) 0}.text-content ::ng-deep th,.text-content ::ng-deep td{border:var(--text-table-border);padding:var(--text-table-padding);vertical-align:middle;text-align:left}.text-content ::ng-deep th{text-align:center;font-weight:700}.text-content.editing{cursor:text}.text-content.editing ::ng-deep [data-embed-key],.text-content.editing ::ng-deep [data-embed-field]{padding:0 2px;border-radius:3px;background-color:#4e79a729;-webkit-user-select:all;user-select:all}.text-content.editing ::ng-deep [data-embed-key]{cursor:pointer}.text-content.editing ::ng-deep [data-embed-key]:hover{background-color:#4e79a747}.text-content.editing ::ng-deep [data-embed-missing]{background-color:#e1575924;outline:1px dashed rgba(225,87,89,.7)}.text-content.editing ::ng-deep th,.text-content.editing ::ng-deep tfoot td{background-color:transparent}.text-content.editing ::ng-deep .fr-table-selector{display:none}.text-content.editing ::ng-deep .fr-table-selection-hover{outline:none}.text-hint{color:#9aa5b1;-webkit-user-select:none;user-select:none}.value-popover-anchor{position:absolute;pointer-events:none}::ng-deep .value-popover-wrapper .popover-content{padding:15px!important}.value-popover{gap:15px}.value-popover-name{font-weight:600}.value-popover-settings{--mdc-icon-button-state-layer-size: 24px;width:24px;height:24px;padding:0}.value-notice{position:absolute;top:100%;left:0;z-index:2;margin-top:4px;padding:2px 4px 2px 10px;border:1px solid #d9dee3;border-radius:4px;background:#fff;white-space:nowrap;font-family:Arial,Helvetica,sans-serif}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FsHtmlEditorComponent, selector: "fs-html-editor", inputs: ["config", "disabled"] }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11775
11775
  }
11776
11776
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentTextComponent, decorators: [{
11777
11777
  type: Component,
@@ -11796,7 +11796,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
11796
11796
  '[style.--text-table-border]': 'tableLook.border',
11797
11797
  '[style.--text-table-padding]': 'tableLook.padding',
11798
11798
  '[style.--text-table-margin]': 'tableLook.margin',
11799
- }, template: "@if (editing()) {\n <!-- Floating fs-html-editor: no frame, Froala's inline toolbar pops up over\n the text. pointerdown stays here so a press in the text never starts a\n drag of the block or a pan of the canvas. -->\n <fs-html-editor\n class=\"text-content editing\"\n [config]=\"editorConfig\"\n [(ngModel)]=\"draft\"\n (pointerdown)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n </fs-html-editor>\n\n <!-- One notice at a time: an edit stopped because it would have removed\n values, an edit that left values no text reads, or a refused command.\n mousedown keeps the editor's focus and selection, so Undo reaches it. -->\n @if (notice(); as current) {\n <div\n class=\"value-notice fs-row.gap-sm\"\n (pointerdown)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.preventDefault()\">\n @switch (current.kind) {\n @case ('blocked') {\n @if (current.block.removeSelection) {\n <span>\n This selection includes {{ current.block.count }} {{ current.block.count === 1 ? 'value' : 'values' }}.\n </span>\n <button\n mat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"removeBlockedSelection()\">\n Remove text and {{ current.block.count === 1 ? 'value' : current.block.count + ' values' }}\n </button>\n } @else {\n <span>To take a value out, select it, then press Delete.</span>\n }\n }\n @case ('unused') {\n <span>{{ current.count }} {{ current.count === 1 ? 'value' : 'values' }} moved to Not used</span>\n <button\n mat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"undoValuesUnused()\">\n Undo\n </button>\n }\n @case ('message') {\n <span>{{ current.text }}</span>\n }\n }\n <button\n mat-button\n type=\"button\"\n (click)=\"dismissNotice()\">\n Dismiss\n </button>\n </div>\n }\n} @else if (editMode() && isBlank()) {\n <!-- An empty block while the canvas is laid out: a hint in the block's own\n type, never its html, so nothing is typed onto it or exported. -->\n <div class=\"text-content text-hint\">Double-click to edit</div>\n} @else {\n <div\n class=\"text-content\"\n [innerHTML]=\"displayHtml()\">\n </div>\n}\n\n<!-- A clicked value chip's popover: an fs-popover on an invisible anchor laid\n over the chip (the chip itself belongs to the editor). Outside the editing\n block, so a popover still open when editing ends closes the usual way. -->\n<span\n #valuePopoverAnchor\n class=\"value-popover-anchor\"\n aria-hidden=\"true\"\n fsPopover\n trigger=\"click\"\n wrapperClass=\"value-popover-wrapper\"\n [template]=\"valuePopoverTemplate\"\n [indication]=\"false\"\n [maxWidth]=\"320\"\n [style.left.px]=\"valuePopover()?.left\"\n [style.top.px]=\"valuePopover()?.top\"\n [style.width.px]=\"valuePopover()?.width\"\n [style.height.px]=\"valuePopover()?.height\">\n</span>\n<ng-template\n #valuePopoverTemplate\n let-popover=\"popover\">\n <!-- What the value is, and its settings (where it can also be deleted); a\n chip naming no value on the page has no settings. -->\n <div class=\"value-popover fs-row\">\n <div class=\"fs-flex\">\n <div class=\"value-popover-name\">{{ valuePopoverValue()?.name ?? valuePopover()?.key }}</div>\n @if (!valuePopoverValue()) {\n <div class=\"small\">Not a value on this page</div>\n }\n </div>\n @if (valuePopoverValue()) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"value-popover-settings\"\n matTooltip=\"Settings\"\n (click)=\"openValueSettings(popover)\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;height:100%}.text-content{display:block;width:100%;height:100%;overflow:hidden;font-family:Arial,Helvetica,sans-serif;font-size:var(--text-size);line-height:1.25;color:var(--text-color);word-wrap:break-word;letter-spacing:normal;word-spacing:normal;font-weight:400;font-style:normal;text-transform:none;white-space:normal}.text-content ::ng-deep p,.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3,.text-content ::ng-deep ul,.text-content ::ng-deep ol{margin:0}.text-content ::ng-deep h1{font-size:var(--text-h1-size)}.text-content ::ng-deep h2{font-size:var(--text-h2-size)}.text-content ::ng-deep h3{font-size:var(--text-h3-size)}.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3{font-weight:700;line-height:1.2}.text-content ::ng-deep ul,.text-content ::ng-deep ol{padding-left:1.4em}.text-content ::ng-deep p:empty:before{content:\"\\200b\"}.text-content ::ng-deep a{color:inherit}.text-content ::ng-deep table{border-collapse:collapse;margin:var(--text-table-margin) 0}.text-content ::ng-deep th,.text-content ::ng-deep td{border:var(--text-table-border);padding:var(--text-table-padding);vertical-align:middle;text-align:left}.text-content ::ng-deep th{text-align:center;font-weight:700}.text-content.editing{overflow:auto;cursor:text}.text-content.editing ::ng-deep [data-embed-key],.text-content.editing ::ng-deep [data-embed-field]{padding:0 2px;border-radius:3px;background-color:#4e79a729;-webkit-user-select:all;user-select:all}.text-content.editing ::ng-deep [data-embed-key]{cursor:pointer}.text-content.editing ::ng-deep [data-embed-key]:hover{background-color:#4e79a747}.text-content.editing ::ng-deep [data-embed-missing]{background-color:#e1575924;outline:1px dashed rgba(225,87,89,.7)}.text-content.editing ::ng-deep th,.text-content.editing ::ng-deep tfoot td{background-color:transparent}.text-content.editing ::ng-deep .fr-table-selector{display:none}.text-content.editing ::ng-deep .fr-table-selection-hover{outline:none}.text-hint{color:#9aa5b1;-webkit-user-select:none;user-select:none}.value-popover-anchor{position:absolute;pointer-events:none}::ng-deep .value-popover-wrapper .popover-content{padding:15px!important}.value-popover{gap:15px}.value-popover-name{font-weight:600}.value-popover-settings{--mdc-icon-button-state-layer-size: 24px;width:24px;height:24px;padding:0}.value-notice{position:absolute;top:100%;left:0;z-index:2;margin-top:4px;padding:2px 4px 2px 10px;border:1px solid #d9dee3;border-radius:4px;background:#fff;white-space:nowrap;font-family:Arial,Helvetica,sans-serif}\n"] }]
11799
+ }, template: "@if (editing()) {\n <!-- Floating fs-html-editor: no frame, Froala's inline toolbar pops up over\n the text. pointerdown stays here so a press in the text never starts a\n drag of the block or a pan of the canvas. -->\n <fs-html-editor\n class=\"text-content editing\"\n [config]=\"editorConfig\"\n [(ngModel)]=\"draft\"\n (pointerdown)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n </fs-html-editor>\n\n <!-- One notice at a time: an edit stopped because it would have removed\n values, an edit that left values no text reads, or a refused command.\n mousedown keeps the editor's focus and selection, so Undo reaches it. -->\n @if (notice(); as current) {\n <div\n class=\"value-notice fs-row.gap-sm\"\n (pointerdown)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.preventDefault()\">\n @switch (current.kind) {\n @case ('blocked') {\n @if (current.block.removeSelection) {\n <span>\n This selection includes {{ current.block.count }} {{ current.block.count === 1 ? 'value' : 'values' }}.\n </span>\n <button\n mat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"removeBlockedSelection()\">\n Remove text and {{ current.block.count === 1 ? 'value' : current.block.count + ' values' }}\n </button>\n } @else {\n <span>To take a value out, select it, then press Delete.</span>\n }\n }\n @case ('unused') {\n <span>{{ current.count }} {{ current.count === 1 ? 'value' : 'values' }} moved to Not used</span>\n <button\n mat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"undoValuesUnused()\">\n Undo\n </button>\n }\n @case ('message') {\n <span>{{ current.text }}</span>\n }\n }\n <button\n mat-button\n type=\"button\"\n (click)=\"dismissNotice()\">\n Dismiss\n </button>\n </div>\n }\n} @else if (editMode() && isBlank()) {\n <!-- An empty block while the canvas is laid out: a hint in the block's own\n type, never its html, so nothing is typed onto it or exported. -->\n <div class=\"text-content text-hint\">Double-click to edit</div>\n} @else {\n <div\n class=\"text-content\"\n [innerHTML]=\"displayHtml()\">\n </div>\n}\n\n<!-- A clicked value chip's popover: an fs-popover on an invisible anchor laid\n over the chip (the chip itself belongs to the editor). Outside the editing\n block, so a popover still open when editing ends closes the usual way. -->\n<span\n #valuePopoverAnchor\n class=\"value-popover-anchor\"\n aria-hidden=\"true\"\n fsPopover\n trigger=\"click\"\n wrapperClass=\"value-popover-wrapper\"\n [template]=\"valuePopoverTemplate\"\n [indication]=\"false\"\n [maxWidth]=\"320\"\n [style.left.px]=\"valuePopover()?.left\"\n [style.top.px]=\"valuePopover()?.top\"\n [style.width.px]=\"valuePopover()?.width\"\n [style.height.px]=\"valuePopover()?.height\">\n</span>\n<ng-template\n #valuePopoverTemplate\n let-popover=\"popover\">\n <!-- What the value is, and its settings (where it can also be deleted); a\n chip naming no value on the page has no settings. -->\n <div class=\"value-popover fs-row\">\n <div class=\"fs-flex\">\n <div class=\"value-popover-name\">{{ valuePopoverValue()?.name ?? valuePopover()?.key }}</div>\n @if (!valuePopoverValue()) {\n <div class=\"small\">Not a value on this page</div>\n }\n </div>\n @if (valuePopoverValue()) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"value-popover-settings\"\n matTooltip=\"Settings\"\n (click)=\"openValueSettings(popover)\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;height:100%}.text-content{display:block;width:100%;height:100%;overflow:visible;font-family:Arial,Helvetica,sans-serif;font-size:var(--text-size);line-height:1.25;color:var(--text-color);word-wrap:break-word;letter-spacing:normal;word-spacing:normal;font-weight:400;font-style:normal;text-transform:none;white-space:normal}.text-content ::ng-deep p,.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3,.text-content ::ng-deep ul,.text-content ::ng-deep ol{margin:0}.text-content ::ng-deep h1{font-size:var(--text-h1-size)}.text-content ::ng-deep h2{font-size:var(--text-h2-size)}.text-content ::ng-deep h3{font-size:var(--text-h3-size)}.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3{font-weight:700;line-height:1.2}.text-content ::ng-deep ul,.text-content ::ng-deep ol{padding-left:1.4em}.text-content ::ng-deep p:empty:before{content:\"\\200b\"}.text-content ::ng-deep a{color:inherit}.text-content ::ng-deep table{border-collapse:collapse;margin:var(--text-table-margin) 0}.text-content ::ng-deep th,.text-content ::ng-deep td{border:var(--text-table-border);padding:var(--text-table-padding);vertical-align:middle;text-align:left}.text-content ::ng-deep th{text-align:center;font-weight:700}.text-content.editing{cursor:text}.text-content.editing ::ng-deep [data-embed-key],.text-content.editing ::ng-deep [data-embed-field]{padding:0 2px;border-radius:3px;background-color:#4e79a729;-webkit-user-select:all;user-select:all}.text-content.editing ::ng-deep [data-embed-key]{cursor:pointer}.text-content.editing ::ng-deep [data-embed-key]:hover{background-color:#4e79a747}.text-content.editing ::ng-deep [data-embed-missing]{background-color:#e1575924;outline:1px dashed rgba(225,87,89,.7)}.text-content.editing ::ng-deep th,.text-content.editing ::ng-deep tfoot td{background-color:transparent}.text-content.editing ::ng-deep .fr-table-selector{display:none}.text-content.editing ::ng-deep .fr-table-selection-hover{outline:none}.text-hint{color:#9aa5b1;-webkit-user-select:none;user-select:none}.value-popover-anchor{position:absolute;pointer-events:none}::ng-deep .value-popover-wrapper .popover-content{padding:15px!important}.value-popover{gap:15px}.value-popover-name{font-weight:600}.value-popover-settings{--mdc-icon-button-state-layer-size: 24px;width:24px;height:24px;padding:0}.value-notice{position:absolute;top:100%;left:0;z-index:2;margin-top:4px;padding:2px 4px 2px 10px;border:1px solid #d9dee3;border-radius:4px;background:#fff;white-space:nowrap;font-family:Arial,Helvetica,sans-serif}\n"] }]
11800
11800
  }], ctorParameters: () => [] });
11801
11801
 
11802
11802
  // Resize handles, named by compass direction.
@@ -15199,12 +15199,11 @@ const KPI_EXPORT_MIN_SCALE = 0.5;
15199
15199
  // failures.
15200
15200
  const EXPORT_CONCURRENCY = 3;
15201
15201
  // What the export gates call the header and footer: when a value they show
15202
- // came back without its number, and when one of their text blocks does not
15203
- // fit its box.
15202
+ // came back without its number, and when one of their blocks could not be
15203
+ // drawn.
15204
15204
  const HEADER_FOOTER_NAME = 'Header & footer';
15205
- // How much of a text block's words the export gates quote to name a block that
15206
- // does not fit its box.
15207
- const OVERFLOW_SNIPPET_LENGTH = 40;
15205
+ // How much of a text block's words the export gates quote to name the block.
15206
+ const TEXT_SNIPPET_LENGTH = 40;
15208
15207
  // What a component with nothing to draw prints in the exported file.
15209
15208
  //
15210
15209
  // The two cases must not read alike. A tile that returned no rows is a finding
@@ -15238,16 +15237,11 @@ function exportTitle(component, tokens, search, crumbs, filtered = false) {
15238
15237
  function isFailed(item) {
15239
15238
  return !item.data && item.component.type !== 'text';
15240
15239
  }
15241
- // A text block that does not fit its box in a built file, as the overflow gate
15242
- // names it: under its page (HEADER_FOOTER_NAME for a header or footer block)
15243
- // and by the start of its words, on one line. Shared by the PDF and the deck.
15244
- function textOverflow(component, page, tokens) {
15240
+ // A text block as the export gates name it: by the start of its words, on one
15241
+ // line.
15242
+ function textSnippet(component, tokens) {
15245
15243
  const text = textPlain(component.config?.html ?? '', tokens).replace(/\s+/g, ' ').trim();
15246
- return {
15247
- componentId: component.id,
15248
- page,
15249
- snippet: text.length > OVERFLOW_SNIPPET_LENGTH ? `${text.slice(0, OVERFLOW_SNIPPET_LENGTH).trimEnd()}…` : text,
15250
- };
15244
+ return text.length > TEXT_SNIPPET_LENGTH ? `${text.slice(0, TEXT_SNIPPET_LENGTH).trimEnd()}…` : text;
15251
15245
  }
15252
15246
  // The lines under the report's name on the PDF title page and the title slide:
15253
15247
  // the export date, the active filters, then what the numbers are measured in.
@@ -15856,7 +15850,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
15856
15850
  // in the report's format context, as on screen.
15857
15851
  async function renderChartImage(component, data, width, height, context, pixelRatio = 3) {
15858
15852
  // The same tree-shaken echarts build + 'report' theme the live canvas uses.
15859
- const echarts = (await import('./firestitch-report-echarts-CUVOSBvJ.mjs')).default;
15853
+ const echarts = (await import('./firestitch-report-echarts-CBBTk1Ql.mjs')).default;
15860
15854
  const host = document.createElement('div');
15861
15855
  host.style.width = `${width}px`;
15862
15856
  host.style.height = `${height}px`;
@@ -15881,17 +15875,12 @@ async function renderChartImage(component, data, width, height, context, pixelRa
15881
15875
  }
15882
15876
  }
15883
15877
 
15884
- // How far past the bottom of its box a block may reach and still fit: two
15885
- // pixels, so the rounding of the last line's edge never counts as overflow.
15886
- // The deck keeps a table's row on the same terms.
15887
- const OVERFLOW_SLACK = 2 / PX_PER_INCH;
15888
15878
  // Where the canvas puts each part of a text block, for the exports. A
15889
15879
  // browser wraps the words and sizes a table's columns by its own layout, which
15890
15880
  // neither jsPDF nor PowerPoint reproduces. So the block is rendered offscreen
15891
15881
  // with the canvas's own component, at the canvas's 96px per inch and unscaled,
15892
15882
  // and the exports place what they draw where it sat: a table at its top, in
15893
15883
  // its column widths and row heights, and the words between tables at theirs.
15894
- // It also tells whether the block runs past the bottom of its box.
15895
15884
  //
15896
15885
  // Provided beside the export services, so the block renders under the same
15897
15886
  // injector the canvas's blocks do.
@@ -15986,32 +15975,22 @@ function blockBox(component, text) {
15986
15975
  }
15987
15976
  // Split the rendered block at its tables, as textBlocks splits the same html,
15988
15977
  // so the nth segment is the nth text part and the nth table the nth table
15989
- // part. How far the block reaches is the bottom of its last part that draws
15990
- // anything.
15978
+ // part.
15991
15979
  function measureBlock(content) {
15992
15980
  const box = content.getBoundingClientRect();
15993
15981
  const parts = splitAtTables(content);
15994
15982
  const segments = [];
15995
15983
  const tables = [];
15996
- let usedHeight = 0;
15997
15984
  parts.forEach((part, index) => {
15998
15985
  if (Array.isArray(part)) {
15999
- const segment = measureSegment(drawnNodes(part, index === parts.length - 1), box);
16000
- segments.push(segment);
16001
- usedHeight = segment?.bottom ?? usedHeight;
15986
+ segments.push(measureSegment(drawnNodes(part, index === parts.length - 1), box));
16002
15987
  }
16003
15988
  else {
16004
- const table = measureTable(part, box);
16005
- tables.push(table);
16006
- usedHeight = table.top + table.height;
15989
+ tables.push(measureTable(part, box));
16007
15990
  }
16008
15991
  });
16009
- const contentHeight = inches(box.height);
16010
15992
  return {
16011
15993
  contentWidth: inches(box.width),
16012
- contentHeight,
16013
- usedHeight,
16014
- overflow: usedHeight > contentHeight + OVERFLOW_SLACK,
16015
15994
  segments,
16016
15995
  tables,
16017
15996
  };
@@ -16020,7 +15999,7 @@ function measureBlock(content) {
16020
15999
  // blocks draws nothing anywhere. A blank line at the start of a block is
16021
16000
  // drawn, on the canvas and by textBlocks alike, but textBlocks drops the blank
16022
16001
  // lines at the very end of a block, so they are not measured either: a
16023
- // trailing empty paragraph never makes a block overflow.
16002
+ // trailing empty paragraph never adds to the last segment's height.
16024
16003
  function drawnNodes(nodes, blockEnd) {
16025
16004
  const skipsLast = blockEnd ? isBlank : isVoid;
16026
16005
  let start = 0;
@@ -16185,9 +16164,6 @@ const TEXT_LINE_HEIGHT = 1.25;
16185
16164
  // How far a list item's text sits from the block's edge, points; the marker
16186
16165
  // hangs in that space.
16187
16166
  const LIST_INDENT = 16;
16188
- // What the export's overflow gate says the PDF does with a text block that does
16189
- // not fit its box.
16190
- const PDF_OVERFLOW_NOTE = 'The PDF cuts it off at the bottom of its box.';
16191
16167
  // How far a table's group label steps in per level, points.
16192
16168
  const GRID_INDENT$1 = 8;
16193
16169
  // Builds the PDF client-side with jspdf. The report's unit is inches and PDFs
@@ -16207,14 +16183,12 @@ const GRID_INDENT$1 = 8;
16207
16183
  // without end trying to place it.
16208
16184
  //
16209
16185
  // A component that fails to draw is marked in its box and the file goes on
16210
- // (_guarded); build() hands back the ones that did, as it does the text it
16211
- // cut.
16186
+ // (_guarded); build() hands back the ones that did.
16212
16187
  //
16213
16188
  // A text block's tables are drawn by autoTable too, from the line its words
16214
16189
  // reach, in the column widths the canvas gives them (ReportTextLayoutService).
16215
- // A text block is cut at the bottom of its box, as the canvas clips it, and
16216
- // build() hands the file back unsaved with the blocks it cut, so the export
16217
- // can ask about them before it saves.
16190
+ // A text block is never cut at the bottom of its box: what runs past it draws
16191
+ // on below, as on the canvas, down to the page's edge.
16218
16192
  class ReportPdfService {
16219
16193
  _textLayout = inject(ReportTextLayoutService);
16220
16194
  async build(collected) {
@@ -16237,8 +16211,6 @@ class ReportPdfService {
16237
16211
  }
16238
16212
  }
16239
16213
  return {
16240
- overflows: [...context.textOverflows.values()],
16241
- overflowNote: PDF_OVERFLOW_NOTE,
16242
16214
  undrawn: [...context.undrawn.values()],
16243
16215
  save: () => {
16244
16216
  context.doc.save(`${this._slug(collected.report.name)}.pdf`);
@@ -16266,7 +16238,6 @@ class ReportPdfService {
16266
16238
  masterValues: this._pdfValues(collected.masterValues),
16267
16239
  filterFields: collected.filterFields,
16268
16240
  masterFilters: layerFilterFields(collected.filterFields, report.filterGroups ?? [], null),
16269
- textOverflows: new Map(),
16270
16241
  undrawn: new Map(),
16271
16242
  };
16272
16243
  }
@@ -16284,8 +16255,8 @@ class ReportPdfService {
16284
16255
  if (item.component.type !== 'text') {
16285
16256
  await this._renderComponent(context, collectedPage.page, item, tokens, overflows);
16286
16257
  }
16287
- else if (await this._renderText(context, item.component, pageTokens)) {
16288
- this._recordOverflow(context, item.component, tokens.pageName, pageTokens);
16258
+ else {
16259
+ await this._renderText(context, item.component, pageTokens);
16289
16260
  }
16290
16261
  });
16291
16262
  }
@@ -16316,7 +16287,7 @@ class ReportPdfService {
16316
16287
  componentId: component.id,
16317
16288
  page,
16318
16289
  name: component.type === 'text'
16319
- ? textOverflow(component, page, tokens).snippet
16290
+ ? textSnippet(component, tokens)
16320
16291
  : exportTitle(component, tokens),
16321
16292
  });
16322
16293
  }
@@ -16343,20 +16314,10 @@ class ReportPdfService {
16343
16314
  const masterTokens = { ...tokens, values: context.masterValues, filters: context.masterFilters };
16344
16315
  for (const component of (context.report.master?.components ?? []).filter(isDrawn)) {
16345
16316
  await this._guarded(context, component, HEADER_FOOTER_NAME, masterTokens, true, async () => {
16346
- if (await this._renderText(context, component, masterTokens)) {
16347
- this._recordOverflow(context, component, HEADER_FOOTER_NAME, masterTokens);
16348
- }
16317
+ await this._renderText(context, component, masterTokens);
16349
16318
  });
16350
16319
  }
16351
16320
  }
16352
- // Lists a text block that was cut, under the page the export's overflow gate
16353
- // names it by. A block cut again on a later page (a header or footer) keeps
16354
- // the first entry.
16355
- _recordOverflow(context, component, page, tokens) {
16356
- if (!context.textOverflows.has(component.id)) {
16357
- context.textOverflows.set(component.id, textOverflow(component, page, tokens));
16358
- }
16359
- }
16360
16321
  _titlePage(doc, collected, pageW, pageH) {
16361
16322
  doc.setFont('helvetica', 'bold');
16362
16323
  doc.setFontSize(28);
@@ -16371,11 +16332,11 @@ class ReportPdfService {
16371
16332
  // words between its tables are drawn line by line (_drawParagraphs) and each
16372
16333
  // table by autoTable from the line the words above it reach
16373
16334
  // (_drawTextTable). Only a block with a table is measured, for the edges
16374
- // and column widths the canvas gives its tables. Whatever runs past
16375
- // the block's height is left off — the canvas clips the block there too.
16376
- // Returns whether anything was.
16335
+ // and column widths the canvas gives its tables. Words and table rows that
16336
+ // run past the block's height draw on below it, as on the canvas; only the
16337
+ // page's edge stops them.
16377
16338
  async _renderText(context, component, tokens) {
16378
- const box = this._textBox(component);
16339
+ const box = this._textBox(component, context.pageH);
16379
16340
  const blocks = textBlocks(component.config?.html ?? '', tokens);
16380
16341
  const layout = blocks.some((block) => block.kind === 'table')
16381
16342
  ? await this._textLayout.measure(component, tokens)
@@ -16391,23 +16352,22 @@ class ReportPdfService {
16391
16352
  ? this._drawParagraphs(context.doc, block.paragraphs, box, cursorY)
16392
16353
  : this._drawTextTable(context, block.table, layout?.tables[tableIndex++] ?? null, box, onMargin ? cursorY : cursorY + TEXT_TABLE_MARGIN);
16393
16354
  if (drawn.cut) {
16394
- return true;
16355
+ return;
16395
16356
  }
16396
16357
  cursorY = drawn.cursorY;
16397
16358
  onMargin = block.kind === 'table' || (onMargin && !block.paragraphs.length);
16398
16359
  }
16399
- return false;
16400
16360
  }
16401
16361
  // Where a text block's words go: inside its padding, each edge 1/8" unless
16402
- // set.
16403
- _textBox(component) {
16362
+ // set, down to the bottom of the page (pageH).
16363
+ _textBox(component, pageH) {
16404
16364
  const padding = component.config?.padding;
16405
16365
  const inset = (value) => (value ?? DEFAULT_COMPONENT_PADDING) * PT;
16406
16366
  return {
16407
16367
  left: component.x * PT + inset(padding?.left),
16408
16368
  top: component.y * PT + inset(padding?.top),
16409
16369
  width: component.w * PT - inset(padding?.left) - inset(padding?.right),
16410
- bottom: (component.y + component.h) * PT - inset(padding?.bottom),
16370
+ bottom: pageH,
16411
16371
  };
16412
16372
  }
16413
16373
  // Paragraphs drawn line by line from cursorY down: each one's runs are broken
@@ -16471,9 +16431,9 @@ class ReportPdfService {
16471
16431
  // evenly. A row is as tall as its cells' words wrapped as _drawParagraphs
16472
16432
  // wraps them, and the words are drawn into each cell once autoTable has
16473
16433
  // drawn its grid lines and fill (_drawTextCell), so they keep their runs'
16474
- // fonts and colors. Only the rows the box holds are drawn, and a table that
16475
- // loses any, or runs past its page anyway, is cut. The cursor it leaves is
16476
- // TEXT_TABLE_MARGIN under it.
16434
+ // fonts and colors. Only the rows that fit above the box's bottom, the
16435
+ // page's edge, are drawn, and a table that loses any, or runs past its page
16436
+ // anyway, is cut. The cursor it leaves is TEXT_TABLE_MARGIN under it.
16477
16437
  _drawTextTable(context, table, layout, box, top) {
16478
16438
  if (top >= box.bottom) {
16479
16439
  return { cursorY: top, cut: true };
@@ -17362,10 +17322,6 @@ const PPTX_CONSTANT_LINE_SIZE = 1;
17362
17322
  // What the upper edge of a reference band is called in the data sheet and the
17363
17323
  // legend, after the band's own label on its lower edge.
17364
17324
  const PPTX_BAND_UPPER_SUFFIX = ' (upper)';
17365
- // What the export's overflow gate says the deck does with a text block that
17366
- // does not fit its box.
17367
- const PPTX_OVERFLOW_NOTE = 'In the PowerPoint it runs past the bottom of its box, and table rows past it are '
17368
- + 'left out.';
17369
17325
  // The shortest text box a text block's words get between its tables, inches,
17370
17326
  // however close the canvas sets their top and bottom.
17371
17327
  const TEXT_SEGMENT_MIN_H = 0.05;
@@ -17416,15 +17372,10 @@ class ReportPptxService {
17416
17372
  pptx.layout = 'REPORT';
17417
17373
  pptx.title = report.name;
17418
17374
  this._titleSlide(pptx, collected, pageW, pageH);
17419
- // The text blocks that do not fit their box, by component id, so a header
17420
- // or footer block is listed once, not once per slide.
17421
- const overflows = new Map();
17422
17375
  for (const index of collected.pages.keys()) {
17423
- await this._renderPage(pptx, collected, index, overflows);
17376
+ await this._renderPage(pptx, collected, index);
17424
17377
  }
17425
17378
  return {
17426
- overflows: [...overflows.values()],
17427
- overflowNote: PPTX_OVERFLOW_NOTE,
17428
17379
  // A slide's shapes are laid out by PowerPoint, so a component cannot
17429
17380
  // fail to draw here as a PDF table can.
17430
17381
  undrawn: [],
@@ -17437,7 +17388,7 @@ class ReportPptxService {
17437
17388
  // components sit on top of them exactly as on the canvas. Each layer's text
17438
17389
  // fills its value tokens with that layer's values: the master's or the
17439
17390
  // page's.
17440
- async _renderPage(pptx, collected, index, overflows) {
17391
+ async _renderPage(pptx, collected, index) {
17441
17392
  const report = collected.report;
17442
17393
  const collectedPage = collected.pages[index];
17443
17394
  const slide = pptx.addSlide();
@@ -17445,37 +17396,27 @@ class ReportPptxService {
17445
17396
  const pageTokens = { ...tokens, values: collectedPage.values };
17446
17397
  const timezone = resolveReportTimezone(report);
17447
17398
  if (!collectedPage.page.masterHidden) {
17448
- await this._renderMaster(slide, collected, tokens, overflows);
17399
+ await this._renderMaster(slide, collected, tokens);
17449
17400
  }
17450
17401
  for (const item of collectedPage.components) {
17451
17402
  if (item.component.type !== 'text') {
17452
17403
  this._renderComponent(pptx, slide, item, tokens, timezone, collected.formatContext);
17453
17404
  }
17454
- else if (await this._renderText(slide, item.component, pageTokens)) {
17455
- this._recordOverflow(overflows, item.component, tokens.pageName, pageTokens);
17405
+ else {
17406
+ await this._renderText(slide, item.component, pageTokens);
17456
17407
  }
17457
17408
  }
17458
17409
  }
17459
17410
  // The header and footer, their value tokens filled with the master's
17460
17411
  // values. They read no page filter: its token prints the em dash there.
17461
- async _renderMaster(slide, collected, tokens, overflows) {
17412
+ async _renderMaster(slide, collected, tokens) {
17462
17413
  const masterTokens = {
17463
17414
  ...tokens,
17464
17415
  values: collected.masterValues,
17465
17416
  filters: layerFilterFields(collected.filterFields, collected.report.filterGroups ?? [], null),
17466
17417
  };
17467
17418
  for (const component of (collected.report.master?.components ?? []).filter(isDrawn)) {
17468
- if (await this._renderText(slide, component, masterTokens)) {
17469
- this._recordOverflow(overflows, component, HEADER_FOOTER_NAME, masterTokens);
17470
- }
17471
- }
17472
- }
17473
- // Lists a text block that does not fit its box, under the page the export's
17474
- // overflow gate names it by. A block listed on an earlier slide (a header or
17475
- // footer) keeps that entry.
17476
- _recordOverflow(overflows, component, page, tokens) {
17477
- if (!overflows.has(component.id)) {
17478
- overflows.set(component.id, textOverflow(component, page, tokens));
17419
+ await this._renderText(slide, component, masterTokens);
17479
17420
  }
17480
17421
  }
17481
17422
  _titleSlide(pptx, collected, pageW, pageH) {
@@ -17497,28 +17438,28 @@ class ReportPptxService {
17497
17438
  });
17498
17439
  }
17499
17440
  // A text block as native PowerPoint text, editable in the deck, each run
17500
- // carrying its own bold/italic/color/size. The block is measured as the
17501
- // canvas lays it out (ReportTextLayoutService). A block without a table is
17502
- // ONE text box (_addTextBox). A block with tables is a stack inside its
17503
- // padding (_renderTextStack). Returns whether anything runs past the bottom
17504
- // of the box or was left out.
17441
+ // carrying its own bold/italic/color/size. A block without a table is ONE
17442
+ // text box (_addTextBox). A block with tables is measured as the canvas lays
17443
+ // it out (ReportTextLayoutService) and drawn as a stack inside its padding
17444
+ // (_renderTextStack). Nothing is cut: what runs past the bottom of the box
17445
+ // draws on below it, as on the canvas.
17505
17446
  async _renderText(slide, component, tokens) {
17506
- const html = component.config?.html ?? '';
17507
- const blocks = textBlocks(html, tokens);
17508
- const layout = await this._textLayout.measure(component, tokens);
17447
+ const blocks = textBlocks(component.config?.html ?? '', tokens);
17509
17448
  const textCount = blocks.filter((block) => block.kind === 'text').length;
17510
17449
  const tableCount = blocks.length - textCount;
17450
+ const layout = tableCount ? await this._textLayout.measure(component, tokens) : null;
17511
17451
  // The measure finds a block's parts one for one with textBlocks, unless the
17512
17452
  // canvas rendered the html differently. A block whose parts it did not
17513
17453
  // find falls back to the one text box, its tables read as lines of text.
17514
- if (!tableCount || layout.tables.length !== tableCount || layout.segments.length !== textCount) {
17454
+ if (!layout || layout.tables.length !== tableCount || layout.segments.length !== textCount) {
17515
17455
  this._addTextBox(slide, component, blockParagraphs(blocks));
17516
- return layout.overflow;
17456
+ return;
17517
17457
  }
17518
- return this._renderTextStack(slide, component, blocks, layout) || layout.overflow;
17458
+ this._renderTextStack(slide, component, blocks, layout);
17519
17459
  }
17520
17460
  // The words as ONE text box over the whole block. Its inset is the block's
17521
- // padding, so the words land where they sat on screen.
17461
+ // padding, so the words land where they sat on screen, and with no autofit
17462
+ // PowerPoint runs what does not fit on past the box's bottom.
17522
17463
  _addTextBox(slide, component, paragraphs) {
17523
17464
  const runs = this._paragraphRuns(paragraphs);
17524
17465
  if (!runs.length) {
@@ -17538,33 +17479,28 @@ class ReportPptxService {
17538
17479
  // words before, between and after its tables, and a native table for each
17539
17480
  // table, each at the top the canvas gives it. PowerPoint spaces lines a
17540
17481
  // little differently from the canvas, so placing every part afresh keeps
17541
- // that drift from adding up down the block. Returns whether any part was
17542
- // left out.
17482
+ // that drift from adding up down the block.
17543
17483
  _renderTextStack(slide, component, blocks, layout) {
17544
17484
  const padding = component.config?.padding;
17545
17485
  const x = component.x + (padding?.left ?? DEFAULT_COMPONENT_PADDING);
17546
17486
  const y = component.y + (padding?.top ?? DEFAULT_COMPONENT_PADDING);
17547
17487
  let segmentIndex = 0;
17548
17488
  let tableIndex = 0;
17549
- let flagged = false;
17550
17489
  for (const block of blocks) {
17551
- const leftOut = block.kind === 'text'
17552
- ? this._addTextSegment(slide, block.paragraphs, layout.segments[segmentIndex++], layout, x, y)
17553
- : this._addTextTable(slide, block.table, layout.tables[tableIndex++], layout, x, y);
17554
- flagged = flagged || leftOut;
17490
+ if (block.kind === 'text') {
17491
+ this._addTextSegment(slide, block.paragraphs, layout.segments[segmentIndex++], layout, x, y);
17492
+ }
17493
+ else {
17494
+ this._addTextTable(slide, block.table, layout.tables[tableIndex++], x, y);
17495
+ }
17555
17496
  }
17556
- return flagged;
17557
17497
  }
17558
17498
  // The words between two tables (or before the first, or after the last) as
17559
17499
  // a text box from the top of their first line to the bottom of their last,
17560
- // as wide as the block's content. A stretch that draws nothing is skipped,
17561
- // and one that starts below the box is left out. Returns whether it was.
17500
+ // as wide as the block's content. A stretch that draws nothing is skipped.
17562
17501
  _addTextSegment(slide, paragraphs, segment, layout, x, y) {
17563
17502
  if (!segment || !paragraphs.some((paragraph) => paragraph.runs.length)) {
17564
- return false;
17565
- }
17566
- if (segment.top >= layout.contentHeight) {
17567
- return true;
17503
+ return;
17568
17504
  }
17569
17505
  slide.addText(this._paragraphRuns(paragraphs), {
17570
17506
  x, y: y + segment.top, w: layout.contentWidth, h: Math.max(TEXT_SEGMENT_MIN_H, segment.bottom - segment.top),
@@ -17573,54 +17509,37 @@ class ReportPptxService {
17573
17509
  fontFace: 'Arial',
17574
17510
  fit: 'none',
17575
17511
  });
17576
- return false;
17577
17512
  }
17578
17513
  // A table as a native, editable PowerPoint table where the canvas puts it,
17579
17514
  // in its column widths and row heights, in the one look every renderer
17580
- // draws (TEXT_TABLE_* in text.ts). Only the rows that end inside the box are
17581
- // kept. Returns whether any row was left out.
17582
- _addTextTable(slide, table, tableLayout, layout, x, y) {
17583
- const kept = this._keptRows(tableLayout, layout.contentHeight);
17584
- if (kept) {
17585
- slide.addTable(this._tableRows(table, kept), {
17586
- x: x + tableLayout.left,
17587
- y: y + tableLayout.top,
17588
- w: tableLayout.width,
17589
- colW: tableLayout.columnWidths,
17590
- rowH: tableLayout.rowHeights.slice(0, kept),
17591
- autoPage: false,
17592
- fontFace: 'Arial',
17593
- fontSize: DEFAULT_TEXT_SIZE,
17594
- color: DEFAULT_TEXT_COLOR.replace('#', ''),
17595
- border: { type: 'solid', pt: TEXT_TABLE_BORDER_WIDTH, color: TEXT_TABLE_BORDER_COLOR.replace('#', '') },
17596
- margin: TEXT_TABLE_CELL_MARGIN,
17597
- });
17515
+ // draws (TEXT_TABLE_* in text.ts). Every row is kept.
17516
+ _addTextTable(slide, table, tableLayout, x, y) {
17517
+ if (!table.rows.length) {
17518
+ return;
17598
17519
  }
17599
- return kept < table.rows.length;
17520
+ slide.addTable(this._tableRows(table), {
17521
+ x: x + tableLayout.left,
17522
+ y: y + tableLayout.top,
17523
+ w: tableLayout.width,
17524
+ colW: tableLayout.columnWidths,
17525
+ rowH: tableLayout.rowHeights,
17526
+ autoPage: false,
17527
+ fontFace: 'Arial',
17528
+ fontSize: DEFAULT_TEXT_SIZE,
17529
+ color: DEFAULT_TEXT_COLOR.replace('#', ''),
17530
+ border: { type: 'solid', pt: TEXT_TABLE_BORDER_WIDTH, color: TEXT_TABLE_BORDER_COLOR.replace('#', '') },
17531
+ margin: TEXT_TABLE_CELL_MARGIN,
17532
+ });
17600
17533
  }
17601
- // How many of a table's rows, from the top, end inside the box.
17602
- _keptRows(tableLayout, contentHeight) {
17603
- let bottom = tableLayout.top;
17604
- let kept = 0;
17605
- for (const rowHeight of tableLayout.rowHeights) {
17606
- bottom += rowHeight;
17607
- if (bottom > contentHeight + OVERFLOW_SLACK) {
17608
- break;
17609
- }
17610
- kept++;
17611
- }
17612
- return kept;
17613
- }
17614
- // The kept rows as pptxgenjs cells, each row in grid order. A PowerPoint
17615
- // table is a full grid, and pptxgenjs counts its columns on the first row
17616
- // and slips a merged cell's continuation into the row below at that cell's
17617
- // place in its own row. So a cell is written in its top left slot only, a
17618
- // slot no cell holds gets an empty cell, and a cell's spans end where they
17619
- // would reach past the kept rows or into a slot another cell holds
17620
- // (claimSlots).
17621
- _tableRows(table, kept) {
17622
- const slots = Array.from({ length: kept }, () => Array(table.columns).fill(null));
17623
- table.rows.slice(0, kept).forEach((row) => row.cells.forEach((cell) => claimSlots(slots, cell)));
17534
+ // The rows as pptxgenjs cells, each row in grid order. A PowerPoint table is
17535
+ // a full grid, and pptxgenjs counts its columns on the first row and slips a
17536
+ // merged cell's continuation into the row below at that cell's place in its
17537
+ // own row. So a cell is written in its top left slot only, a slot no cell
17538
+ // holds gets an empty cell, and a cell's spans end where they would reach
17539
+ // past the last row or into a slot another cell holds (claimSlots).
17540
+ _tableRows(table) {
17541
+ const slots = Array.from({ length: table.rows.length }, () => Array(table.columns).fill(null));
17542
+ table.rows.forEach((row) => row.cells.forEach((cell) => claimSlots(slots, cell)));
17624
17543
  return slots.map((row, rowIndex) => row.flatMap((cell, column) => {
17625
17544
  if (!cell) {
17626
17545
  return [{ text: '' }];
@@ -18581,9 +18500,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
18581
18500
  // Gives a text table's cell the grid slots it covers from its top left: as
18582
18501
  // many columns as are free in its top row, and as many rows down as are free
18583
18502
  // across those columns, at most its own spans. A slot is free until a cell
18584
- // placed before it holds it, and the grid holds only the kept rows. So a span
18585
- // the box cuts ends at the last kept row, and one the html makes overlap
18586
- // another cell (which a browser draws on top of it) ends where they meet.
18503
+ // placed before it holds it. So a span the html takes past the last row ends
18504
+ // there, and one it makes overlap another cell (which a browser draws on top
18505
+ // of it) ends where they meet.
18587
18506
  function claimSlots(slots, cell) {
18588
18507
  const free = (row, column) => row < slots.length && column < slots[row].length && !slots[row][column];
18589
18508
  const columns = (count) => Array.from({ length: count }, (_, offset) => cell.column + offset);
@@ -18609,8 +18528,7 @@ const RETRY = 'retry';
18609
18528
  const DOWNLOAD = 'download';
18610
18529
  // Runs a report export end to end: collect every tile and value, refuse to
18611
18530
  // write a file that is missing any of them without saying so, build it, refuse
18612
- // to save one that could not draw a component, or whose text does not fit its
18613
- // boxes, without saying so, then save.
18531
+ // to save one that could not draw a component without saying so, then save.
18614
18532
  //
18615
18533
  // The gates are the reason this sits between the collector and the renderers
18616
18534
  // rather than in either of them. A deck with five blank pages is worse than no
@@ -18621,9 +18539,9 @@ const DOWNLOAD = 'download';
18621
18539
  // this is the only place that sees it.
18622
18540
  //
18623
18541
  // Failures are decided before the build, which is the expensive part (every
18624
- // chart drawn offscreen, every text block measured), so neither a Retry nor a
18625
- // Cancel pays for one. Text that does not fit its box is known only after it:
18626
- // the build is what measures it.
18542
+ // chart drawn offscreen, every text table measured), so neither a Retry nor a
18543
+ // Cancel pays for one. A component that could not be drawn is known only after
18544
+ // it: the build is what draws it.
18627
18545
  class ReportExportService {
18628
18546
  _collector = inject(ReportExportCollectorService);
18629
18547
  _pdfService = inject(ReportPdfService);
@@ -18649,12 +18567,11 @@ class ReportExportService {
18649
18567
  ? this._run(report, listRows, build)
18650
18568
  : this._buildAndSave(collected, build)))));
18651
18569
  }
18652
- // Builds the file, then saves it once the undrawn and overflow gates let it
18653
- // through.
18570
+ // Builds the file, then saves it once the undrawn gate lets it through.
18654
18571
  _buildAndSave(collected, build) {
18655
18572
  return from(build(collected))
18656
18573
  .pipe(switchMap((built) => this._undrawnGate(built)
18657
- .pipe(switchMap(() => this._overflowGate(built)), switchMap(() => from(Promise.resolve(built.save()))))));
18574
+ .pipe(switchMap(() => from(Promise.resolve(built.save()))))));
18658
18575
  }
18659
18576
  // Resolves to DOWNLOAD when the export is whole (or the user accepted an
18660
18577
  // incomplete one) and to RETRY when they want another attempt. Cancelling
@@ -18704,31 +18621,6 @@ class ReportExportService {
18704
18621
  })
18705
18622
  .pipe(catchError(() => EMPTY));
18706
18623
  }
18707
- // Resolves to DOWNLOAD when every text block fits its box in the built file,
18708
- // or the user accepted one that does not. There is no Retry: the words are
18709
- // the report's own, so a second attempt would build the same file. Each block
18710
- // is named by its page and the start of its words, and the note says what
18711
- // this format does with the rest. Cancelling ends the export quietly, as at
18712
- // the failure gate, and the built file is dropped unsaved.
18713
- _overflowGate(built) {
18714
- if (!built.overflows.length) {
18715
- return of(DOWNLOAD);
18716
- }
18717
- // A block with no words to quote (an empty table, say) is named by its
18718
- // page alone, not by an empty pair of quotes.
18719
- const blocks = built.overflows
18720
- .map((overflow) => `<strong>${escapeHtml(overflow.page)}</strong>`
18721
- + (overflow.snippet ? ` (“${escapeHtml(overflow.snippet)}”)` : ''));
18722
- return this._prompt.confirm({
18723
- title: 'Text does not fit',
18724
- template: `Some text does not fit its box: ${blocks.join(', ')}. ${built.overflowNote}`,
18725
- buttons: [
18726
- { label: 'Download Anyway', value: DOWNLOAD, color: 'primary' },
18727
- { label: 'Cancel', cancel: true, color: '' },
18728
- ],
18729
- })
18730
- .pipe(catchError(() => EMPTY));
18731
- }
18732
18624
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportExportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
18733
18625
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportExportService });
18734
18626
  }
@@ -19549,7 +19441,7 @@ class FsAiReportComponent {
19549
19441
  ReportAiChatData,
19550
19442
  { provide: FsAiChatData, useExisting: ReportAiChatData },
19551
19443
  // Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
19552
- provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CUVOSBvJ.mjs').then((module) => module.default) }),
19444
+ provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CBBTk1Ql.mjs').then((module) => module.default) }),
19553
19445
  // The bar's date pickers start their weeks on Monday and list the built-in
19554
19446
  // presets; a preset only sets the days.
19555
19447
  provideReportDatePicker(),
@@ -19573,7 +19465,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
19573
19465
  ReportAiChatData,
19574
19466
  { provide: FsAiChatData, useExisting: ReportAiChatData },
19575
19467
  // Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
19576
- provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CUVOSBvJ.mjs').then((module) => module.default) }),
19468
+ provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CBBTk1Ql.mjs').then((module) => module.default) }),
19577
19469
  // The bar's date pickers start their weeks on Monday and list the built-in
19578
19470
  // presets; a preset only sets the days.
19579
19471
  provideReportDatePicker(),
@@ -19666,4 +19558,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
19666
19558
  */
19667
19559
 
19668
19560
  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 };
19669
- //# sourceMappingURL=firestitch-report-firestitch-report-CfvBTMrG.mjs.map
19561
+ //# sourceMappingURL=firestitch-report-firestitch-report-Cl_JOBli.mjs.map