@dereekb/dbx-web 13.32.0 → 13.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { Injectable, input, inject, effect, Directive, NgModule, ChangeDetectionStrategy, Component, output, computed, HostListener, forwardRef, model, InjectionToken, viewChild, ElementRef, makeEnvironmentProviders, untracked, ApplicationRef, Injector, EnvironmentInjector, createComponent, DestroyRef, signal, TemplateRef, SecurityContext, ViewContainerRef, booleanAttribute, Renderer2 } from '@angular/core';
3
- import { asPromise, DASH_CHARACTER_PREFIX_INSTANCE, hashStringToNumber, cssTokenVar, useIterableOrValue, isDefinedAndNotFalse, spaceSeparatedCssClasses, cssClassesSet, objectHasNoKeys, getValueFromGetter, firstValue, filterUndefinedValues, separateValues, splitFront, asDecisionFunction, SLASH_PATH_FILE_TYPE_SEPARATOR, filterMaybeArrayValues, mapIterable, toReadableError, isDefaultReadableError, MS_IN_SECOND, mergeObjects, build, ServerErrorResponse, UnauthorizedServerErrorResponse, makeTimer, MS_IN_MINUTE, toggleTimerRunning, unixDateTimeSecondsNumberForNow, ModelRelationUtility, encodeModelKeyTypePair, safeCompareEquality, addMilliseconds, isPast, asArray, slashPathDetails, mimeTypeForFileExtension, slashPathDirectoryTree, nameToInitials, isMaybeNot, isNotFalse, modifier, combineMaps, addModifiers, removeModifiers, applyBestFit, findNext, maybeModifierMapToFunction, makeValuesGroupMap, compareWithMappedValuesFunction, invertMaybeBoolean, splitCommaSeparatedStringToSet, ZIP_FILE_MIME_TYPE, cachedGetter, sortByNumberFunction, JPEG_MIME_TYPES, JPEG_MIME_TYPE, PNG_MIME_TYPE, PDF_MIME_TYPE, sequentialIncrementingNumberStringModelIdFactory, PDF_HEADER, PDF_EOF_MARKER, PDF_ENCRYPT_MARKER } from '@dereekb/util';
3
+ import { asPromise, DASH_CHARACTER_PREFIX_INSTANCE, hashStringToNumber, cssTokenVar, useIterableOrValue, isDefinedAndNotFalse, spaceSeparatedCssClasses, cssClassesSet, objectHasNoKeys, getValueFromGetter, firstValue, filterUndefinedValues, separateValues, splitFront, asDecisionFunction, SLASH_PATH_FILE_TYPE_SEPARATOR, filterMaybeArrayValues, mapIterable, toReadableError, isDefaultReadableError, MS_IN_SECOND, mergeObjects, build, ServerErrorResponse, UnauthorizedServerErrorResponse, makeTimer, MS_IN_MINUTE, toggleTimerRunning, unixDateTimeSecondsNumberForNow, ModelRelationUtility, encodeModelKeyTypePair, safeCompareEquality, addMilliseconds, isPast, asArray, slashPathDetails, mimeTypeForFileExtension, slashPathDirectoryTree, nameToInitials, isMaybeNot, isNotFalse, modifier, combineMaps, addModifiers, removeModifiers, applyBestFit, findNext, maybeModifierMapToFunction, makeValuesGroupMap, compareWithMappedValuesFunction, invertMaybeBoolean, splitCommaSeparatedStringToSet, ZIP_FILE_MIME_TYPE, cachedGetter, sortByNumberFunction, JPEG_MIME_TYPES, JPEG_MIME_TYPE, PNG_MIME_TYPE, PDF_MIME_TYPE, JSON_MIME_TYPE, sequentialIncrementingNumberStringModelIdFactory, PDF_HEADER, PDF_EOF_MARKER, PDF_ENCRYPT_MARKER } from '@dereekb/util';
4
4
  import * as i1$2 from '@dereekb/dbx-core';
5
5
  import { completeOnDestroy, clean, AbstractTransitionWatcherDirective, DbxInjectionComponent, dbxActionWorkProgress, AbstractDbxButtonDirective, hasNonTrivialChildNodes, provideDbxButton, DbxCoreButtonModule, createInjectorForInjectionComponentConfig, initInjectionComponent, AbstractDbxActionValueGetterDirective, cleanSubscription, AbstractDbxActionHandlerDirective, FilterSourceDirective, provideActionStoreSource, isClickableFilterPreset, AbstractDbxAnchorDirective, expandClickableAnchorLinkTrees, DbxCoreFilterModule, DbxButton, DbxActionWorkable, provideDbxActionWorkable, DbxActionContextStoreSourceInstance, cleanSubscriptionWithLockSet, DBX_INJECTION_COMPONENT_DATA, checkNgContentWrapperHasContent, cleanLoadingContext, DbxActionSourceDirective, DbxActionSuccessHandlerDirective, DbxActionDirective, transformEmptyStringInputToUndefined, isIdleActionState, canTriggerAction, DbxCoreActionModule, DbxActionButtonDirective, onDbxAppAuth, SimpleStorageAccessorFactory, mergeStaticProviders, asSegueRef, AbstractTransitionDirective, DbxRouterService, AbstractIfDirective, isSegueRefActive, anchorTypeForAnchor } from '@dereekb/dbx-core';
6
6
  import { NgPopoverRef, NgOverlayContainerService } from 'ng-overlay-container';
@@ -52,7 +52,7 @@ import * as i1$8 from '@angular/material/chips';
52
52
  import { MatChipsModule } from '@angular/material/chips';
53
53
  import * as i2$1 from '@angular/material/tooltip';
54
54
  import { MatTooltipModule } from '@angular/material/tooltip';
55
- import { PDFDocument } from '@cantoo/pdf-lib';
55
+ import { PDFName, PDFString, PDFHexString, PDFDocument, degrees } from '@cantoo/pdf-lib';
56
56
  import { moveItemInArray, CdkDrag, CdkDragHandle, CdkDragPlaceholder, CdkDropList } from '@angular/cdk/drag-drop';
57
57
 
58
58
  /**
@@ -1048,8 +1048,12 @@ class DbxColorDirective {
1048
1048
  /**
1049
1049
  * Background tone level (0-100). When set, the background becomes semi-transparent
1050
1050
  * and text color switches to the vibrant theme color for a tonal appearance.
1051
+ *
1052
+ * Declared as a `model` for the same reason as {@link dbxColor}: a colored-surface component on the same host (e.g.
1053
+ * `dbx-avatar-view`) resolves its tone from its own inputs/context and pushes it here, keeping this directive the
1054
+ * single token provider on the element.
1051
1055
  */
1052
- dbxColorTone = input(...(ngDevMode ? [undefined, { debugName: "dbxColorTone" }] : /* istanbul ignore next */ []));
1056
+ dbxColorTone = model(...(ngDevMode ? [undefined, { debugName: "dbxColorTone" }] : /* istanbul ignore next */ []));
1053
1057
  /**
1054
1058
  * Whether a color is currently bound. When nothing is bound the directive is fully inert — it adds no marker class
1055
1059
  * and no token class — so color tokens inherited from a `[dbxColor]` ancestor pass through untouched (important for
@@ -1126,7 +1130,7 @@ class DbxColorDirective {
1126
1130
  return tone == null ? null : `${tone}%`;
1127
1131
  }, ...(ngDevMode ? [{ debugName: "bgToneStyleSignal" }] : /* istanbul ignore next */ []));
1128
1132
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxColorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1129
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.11", type: DbxColorDirective, isStandalone: true, selector: "[dbxColor]", inputs: { dbxColor: { classPropertyName: "dbxColor", publicName: "dbxColor", isSignal: true, isRequired: false, transformFunction: null }, dbxColorTone: { classPropertyName: "dbxColorTone", publicName: "dbxColorTone", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dbxColor: "dbxColorChange" }, host: { properties: { "class": "cssClassSignal()", "class.dbx-color": "hasColorSignal()", "class.dbx-color-tonal": "isTonalSignal()", "style.--dbx-color-bg-tone": "bgToneStyleSignal()", "style.--dbx-bg-color-current": "bgColorStyleSignal()", "style.--dbx-color-current": "colorStyleSignal()" } }, ngImport: i0 });
1133
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.11", type: DbxColorDirective, isStandalone: true, selector: "[dbxColor]", inputs: { dbxColor: { classPropertyName: "dbxColor", publicName: "dbxColor", isSignal: true, isRequired: false, transformFunction: null }, dbxColorTone: { classPropertyName: "dbxColorTone", publicName: "dbxColorTone", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dbxColor: "dbxColorChange", dbxColorTone: "dbxColorToneChange" }, host: { properties: { "class": "cssClassSignal()", "class.dbx-color": "hasColorSignal()", "class.dbx-color-tonal": "isTonalSignal()", "style.--dbx-color-bg-tone": "bgToneStyleSignal()", "style.--dbx-bg-color-current": "bgColorStyleSignal()", "style.--dbx-color-current": "colorStyleSignal()" } }, ngImport: i0 });
1130
1134
  }
1131
1135
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxColorDirective, decorators: [{
1132
1136
  type: Directive,
@@ -1142,7 +1146,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
1142
1146
  },
1143
1147
  standalone: true
1144
1148
  }]
1145
- }], propDecorators: { dbxColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "dbxColor", required: false }] }, { type: i0.Output, args: ["dbxColorChange"] }], dbxColorTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "dbxColorTone", required: false }] }] } });
1149
+ }], propDecorators: { dbxColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "dbxColor", required: false }] }, { type: i0.Output, args: ["dbxColorChange"] }], dbxColorTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "dbxColorTone", required: false }] }, { type: i0.Output, args: ["dbxColorToneChange"] }] } });
1146
1150
 
1147
1151
  /**
1148
1152
  * Injection token for providing a global array of progress button configurations.
@@ -7795,6 +7799,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
7795
7799
  * Displays a confirmation dialog when the action is triggered. If the user confirms,
7796
7800
  * the configured ready value is passed to the action. If the user cancels, the action is rejected.
7797
7801
  *
7802
+ * A config with `autoConfirm` true shows no dialog and marks the action ready immediately. This lets a
7803
+ * template bind the same input for actions that need confirmation and actions that do not.
7804
+ *
7798
7805
  * This directive works with action triggering only, not button clicks.
7799
7806
  * For button-based confirmation, use an appPromptConfirmButton directive instead.
7800
7807
  *
@@ -7824,7 +7831,7 @@ class DbxActionConfirmDirective extends AbstractPromptConfirmDirective {
7824
7831
  cleanSubscriptionWithLockSet({
7825
7832
  lockSet: this.source.lockSet,
7826
7833
  sub: this.source.triggered$.subscribe(() => {
7827
- const skip = this.dbxActionConfirmSkip();
7834
+ const skip = this.dbxActionConfirmSkip() || this.dbxActionConfirm()?.autoConfirm === true;
7828
7835
  if (skip) {
7829
7836
  this._handleDialogResult(true);
7830
7837
  }
@@ -9611,11 +9618,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9611
9618
  const DBX_AVATAR_CONTEXT_DATA_TOKEN = new InjectionToken('DbxAvatarContextData');
9612
9619
 
9613
9620
  /**
9614
- * Displays an avatar image with automatic fallback to a Material icon when no image is available or when the image fails to load.
9621
+ * Displays an avatar image with automatic fallback to name initials and then a Material icon when no image is
9622
+ * available or when the image fails to load.
9615
9623
  *
9616
9624
  * Supports circle and square styles, and can optionally hide entirely on image error.
9617
9625
  * Receives context via the {@link DBX_AVATAR_CONTEXT_DATA_TOKEN} injection token or direct inputs.
9618
9626
  *
9627
+ * In tile mode the host also takes the `dbx-icon-tile` class, so the `--dbx-icon-tile-*` customizations and the
9628
+ * tile's own `.dbx-color` surface rule apply to the avatar. The color is pushed into the host
9629
+ * {@link DbxColorDirective} rather than bound by the caller, since the avatar view is usually created dynamically
9630
+ * by {@link DbxAvatarComponent} and has no template bindings of its own.
9631
+ *
9619
9632
  * @example
9620
9633
  * ```html
9621
9634
  * <dbx-avatar-view [avatarUrl]="user.photoUrl" [avatarStyle]="'circle'" [avatarIcon]="'account_circle'"></dbx-avatar-view>
@@ -9632,13 +9645,44 @@ class DbxAvatarViewComponent {
9632
9645
  avatarStyle = input(...(ngDevMode ? [undefined, { debugName: "avatarStyle" }] : /* istanbul ignore next */ []));
9633
9646
  avatarIcon = input(...(ngDevMode ? [undefined, { debugName: "avatarIcon" }] : /* istanbul ignore next */ []));
9634
9647
  avatarHideOnError = input(...(ngDevMode ? [undefined, { debugName: "avatarHideOnError" }] : /* istanbul ignore next */ []));
9648
+ avatarTile = input(...(ngDevMode ? [undefined, { debugName: "avatarTile" }] : /* istanbul ignore next */ []));
9649
+ avatarColor = input(...(ngDevMode ? [undefined, { debugName: "avatarColor" }] : /* istanbul ignore next */ []));
9650
+ avatarColorTone = input(...(ngDevMode ? [undefined, { debugName: "avatarColorTone" }] : /* istanbul ignore next */ []));
9651
+ avatarImageFit = input(...(ngDevMode ? [undefined, { debugName: "avatarImageFit" }] : /* istanbul ignore next */ []));
9652
+ avatarImageFilter = input(...(ngDevMode ? [undefined, { debugName: "avatarImageFilter" }] : /* istanbul ignore next */ []));
9653
+ avatarIgnoreDefaultUrl = input(...(ngDevMode ? [undefined, { debugName: "avatarIgnoreDefaultUrl" }] : /* istanbul ignore next */ []));
9654
+ constructor() {
9655
+ const dbxColorDirective = inject(DbxColorDirective, { self: true });
9656
+ // Pushes the resolved color/tone into the host DbxColorDirective, which provides the color tokens and the
9657
+ // `.dbx-color` marker the surface paints from. Both are models for exactly this: the avatar view is created
9658
+ // dynamically, so the caller has no element to bind the directive's inputs on.
9659
+ effect(() => {
9660
+ dbxColorDirective.dbxColor.set(this.avatarColorSignal());
9661
+ dbxColorDirective.dbxColorTone.set(this.avatarColorToneSignal());
9662
+ });
9663
+ }
9635
9664
  avatarHideOnErrorSignal = computed(() => {
9636
9665
  return this.avatarHideOnError() ?? this.defaultContext?.hideOnError ?? false;
9637
9666
  }, ...(ngDevMode ? [{ debugName: "avatarHideOnErrorSignal" }] : /* istanbul ignore next */ []));
9667
+ avatarTileSignal = computed(() => this.avatarTile() ?? this.defaultContext?.tile ?? false, ...(ngDevMode ? [{ debugName: "avatarTileSignal" }] : /* istanbul ignore next */ []));
9668
+ avatarColorSignal = computed(() => this.avatarColor() ?? this.defaultContext?.color, ...(ngDevMode ? [{ debugName: "avatarColorSignal" }] : /* istanbul ignore next */ []));
9669
+ avatarColorToneSignal = computed(() => this.avatarColorTone() ?? this.defaultContext?.colorTone, ...(ngDevMode ? [{ debugName: "avatarColorToneSignal" }] : /* istanbul ignore next */ []));
9670
+ avatarImageFilterSignal = computed(() => this.avatarImageFilter() ?? this.defaultContext?.imageFilter, ...(ngDevMode ? [{ debugName: "avatarImageFilterSignal" }] : /* istanbul ignore next */ []));
9671
+ /**
9672
+ * A tile is a branded surface, so its image defaults to `'contain'` — cropping a logo is never right.
9673
+ */
9674
+ avatarImageFitSignal = computed(() => {
9675
+ const avatarTile = this.avatarTileSignal();
9676
+ return this.avatarImageFit() ?? this.defaultContext?.imageFit ?? (avatarTile ? 'contain' : 'cover');
9677
+ }, ...(ngDevMode ? [{ debugName: "avatarImageFitSignal" }] : /* istanbul ignore next */ []));
9678
+ avatarIgnoreDefaultUrlSignal = computed(() => this.avatarIgnoreDefaultUrl() ?? this.defaultContext?.ignoreDefaultUrl ?? false, ...(ngDevMode ? [{ debugName: "avatarIgnoreDefaultUrlSignal" }] : /* istanbul ignore next */ []));
9638
9679
  currentAvatarUrlSignal = computed(() => {
9639
9680
  const directUrl = this.avatarUrl();
9640
9681
  const contextUrl = this.defaultContext?.url;
9641
- const url = directUrl ?? contextUrl ?? this.defaultAvatarUrl ?? undefined;
9682
+ // the service default is an app-wide person placeholder; an avatar that means to fall through to its icon
9683
+ // (a provider tile, say) opts out of it rather than rendering someone else's placeholder.
9684
+ const serviceDefaultUrl = this.avatarIgnoreDefaultUrlSignal() ? undefined : this.defaultAvatarUrl;
9685
+ const url = directUrl ?? contextUrl ?? serviceDefaultUrl ?? undefined;
9642
9686
  return url;
9643
9687
  }, ...(ngDevMode ? [{ debugName: "currentAvatarUrlSignal" }] : /* istanbul ignore next */ []));
9644
9688
  hasAvatarErrorSignal = computed(() => {
@@ -9684,9 +9728,16 @@ class DbxAvatarViewComponent {
9684
9728
  const initials = this.initialsSignal();
9685
9729
  return !this.avatarUrlSignal() && !!initials;
9686
9730
  }, ...(ngDevMode ? [{ debugName: "hasInitialsSignal" }] : /* istanbul ignore next */ []));
9731
+ /**
9732
+ * The curated color the initials are painted on.
9733
+ *
9734
+ * Null when an explicit color is set: the initials surface is 100% of the avatar box, so a curated color would
9735
+ * cover the host's painted surface entirely, and the host's inherited `--dbx-color-bg-tone` would wash it out.
9736
+ */
9687
9737
  curatedColorSignal = computed(() => {
9688
9738
  const name = this.nameSignal();
9689
- return name ? (this.colorService?.getCuratedColorForValue(name) ?? dbxCuratedColorConfigForString(name)) : null;
9739
+ const hasExplicitColor = Boolean(this.avatarColorSignal());
9740
+ return name && !hasExplicitColor ? (this.colorService?.getCuratedColorForValue(name) ?? dbxCuratedColorConfigForString(name)) : null;
9690
9741
  }, ...(ngDevMode ? [{ debugName: "curatedColorSignal" }] : /* istanbul ignore next */ []));
9691
9742
  onAvatarImageError(event) {
9692
9743
  if (event.target) {
@@ -9698,9 +9749,9 @@ class DbxAvatarViewComponent {
9698
9749
  }
9699
9750
  }
9700
9751
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxAvatarViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9701
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxAvatarViewComponent, isStandalone: true, selector: "dbx-avatar-view", inputs: { avatarUrl: { classPropertyName: "avatarUrl", publicName: "avatarUrl", isSignal: true, isRequired: false, transformFunction: null }, avatarName: { classPropertyName: "avatarName", publicName: "avatarName", isSignal: true, isRequired: false, transformFunction: null }, avatarStyle: { classPropertyName: "avatarStyle", publicName: "avatarStyle", isSignal: true, isRequired: false, transformFunction: null }, avatarIcon: { classPropertyName: "avatarIcon", publicName: "avatarIcon", isSignal: true, isRequired: false, transformFunction: null }, avatarHideOnError: { classPropertyName: "avatarHideOnError", publicName: "avatarHideOnError", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.dbx-avatar-view-error": "hasAvatarErrorSignal()", "class.dbx-avatar-view-circle": "avatarStyleClassSignal() === \"circle\"", "class.dbx-avatar-view-square": "avatarStyleClassSignal() === \"square\"", "class.dbx-avatar-view-with-avatar": "hasAvatarSignal()", "class.dbx-avatar-view-no-avatar": "missingAvatarSignal()", "class.dbx-avatar-view-with-initials": "hasInitialsSignal()", "class.dbx-avatar-view-hide-avatar": "hideAvatarSignal()" }, classAttribute: "dbx-avatar-view" }, ngImport: i0, template: `
9752
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxAvatarViewComponent, isStandalone: true, selector: "dbx-avatar-view", inputs: { avatarUrl: { classPropertyName: "avatarUrl", publicName: "avatarUrl", isSignal: true, isRequired: false, transformFunction: null }, avatarName: { classPropertyName: "avatarName", publicName: "avatarName", isSignal: true, isRequired: false, transformFunction: null }, avatarStyle: { classPropertyName: "avatarStyle", publicName: "avatarStyle", isSignal: true, isRequired: false, transformFunction: null }, avatarIcon: { classPropertyName: "avatarIcon", publicName: "avatarIcon", isSignal: true, isRequired: false, transformFunction: null }, avatarHideOnError: { classPropertyName: "avatarHideOnError", publicName: "avatarHideOnError", isSignal: true, isRequired: false, transformFunction: null }, avatarTile: { classPropertyName: "avatarTile", publicName: "avatarTile", isSignal: true, isRequired: false, transformFunction: null }, avatarColor: { classPropertyName: "avatarColor", publicName: "avatarColor", isSignal: true, isRequired: false, transformFunction: null }, avatarColorTone: { classPropertyName: "avatarColorTone", publicName: "avatarColorTone", isSignal: true, isRequired: false, transformFunction: null }, avatarImageFit: { classPropertyName: "avatarImageFit", publicName: "avatarImageFit", isSignal: true, isRequired: false, transformFunction: null }, avatarImageFilter: { classPropertyName: "avatarImageFilter", publicName: "avatarImageFilter", isSignal: true, isRequired: false, transformFunction: null }, avatarIgnoreDefaultUrl: { classPropertyName: "avatarIgnoreDefaultUrl", publicName: "avatarIgnoreDefaultUrl", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.dbx-avatar-view-error": "hasAvatarErrorSignal()", "class.dbx-avatar-view-circle": "avatarStyleClassSignal() === \"circle\"", "class.dbx-avatar-view-square": "avatarStyleClassSignal() === \"square\"", "class.dbx-avatar-view-with-avatar": "hasAvatarSignal()", "class.dbx-avatar-view-no-avatar": "missingAvatarSignal()", "class.dbx-avatar-view-with-initials": "hasInitialsSignal()", "class.dbx-avatar-view-hide-avatar": "hideAvatarSignal()", "class.dbx-icon-tile": "avatarTileSignal()", "class.dbx-avatar-view-tile": "avatarTileSignal()" }, classAttribute: "dbx-avatar-view" }, hostDirectives: [{ directive: DbxColorDirective }], ngImport: i0, template: `
9702
9753
  @if (avatarUrlSignal()) {
9703
- <img (error)="onAvatarImageError($event)" class="dbx-avatar-view-img" [src]="avatarUrlSignal()!" alt="." loading="lazy" decoding="async" />
9754
+ <img (error)="onAvatarImageError($event)" class="dbx-avatar-view-img" [class.dbx-avatar-view-img-contain]="avatarImageFitSignal() === 'contain'" [style.filter]="avatarImageFilterSignal()" [src]="avatarUrlSignal()!" alt="." loading="lazy" decoding="async" />
9704
9755
  } @else if (initialsSignal()) {
9705
9756
  <div class="dbx-avatar-view-initials" [dbxColor]="curatedColorSignal()" aria-hidden="true">{{ initialsSignal() }}</div>
9706
9757
  } @else {
@@ -9708,7 +9759,7 @@ class DbxAvatarViewComponent {
9708
9759
  <mat-icon>{{ avatarIconSignal() }}</mat-icon>
9709
9760
  </div>
9710
9761
  }
9711
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: DbxColorDirective, selector: "[dbxColor]", inputs: ["dbxColor", "dbxColorTone"], outputs: ["dbxColorChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9762
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: DbxColorDirective, selector: "[dbxColor]", inputs: ["dbxColor", "dbxColorTone"], outputs: ["dbxColorChange", "dbxColorToneChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9712
9763
  }
9713
9764
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxAvatarViewComponent, decorators: [{
9714
9765
  type: Component,
@@ -9716,7 +9767,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9716
9767
  selector: 'dbx-avatar-view',
9717
9768
  template: `
9718
9769
  @if (avatarUrlSignal()) {
9719
- <img (error)="onAvatarImageError($event)" class="dbx-avatar-view-img" [src]="avatarUrlSignal()!" alt="." loading="lazy" decoding="async" />
9770
+ <img (error)="onAvatarImageError($event)" class="dbx-avatar-view-img" [class.dbx-avatar-view-img-contain]="avatarImageFitSignal() === 'contain'" [style.filter]="avatarImageFilterSignal()" [src]="avatarUrlSignal()!" alt="." loading="lazy" decoding="async" />
9720
9771
  } @else if (initialsSignal()) {
9721
9772
  <div class="dbx-avatar-view-initials" [dbxColor]="curatedColorSignal()" aria-hidden="true">{{ initialsSignal() }}</div>
9722
9773
  } @else {
@@ -9733,13 +9784,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9733
9784
  '[class.dbx-avatar-view-with-avatar]': 'hasAvatarSignal()',
9734
9785
  '[class.dbx-avatar-view-no-avatar]': 'missingAvatarSignal()',
9735
9786
  '[class.dbx-avatar-view-with-initials]': 'hasInitialsSignal()',
9736
- '[class.dbx-avatar-view-hide-avatar]': 'hideAvatarSignal()'
9787
+ '[class.dbx-avatar-view-hide-avatar]': 'hideAvatarSignal()',
9788
+ // both: `dbx-icon-tile` brings the tile's presentation and customization vocabulary, and
9789
+ // `dbx-avatar-view-tile` is the avatar-owned marker its overrides are scoped on.
9790
+ '[class.dbx-icon-tile]': 'avatarTileSignal()',
9791
+ '[class.dbx-avatar-view-tile]': 'avatarTileSignal()'
9737
9792
  },
9738
9793
  imports: [MatIconModule, DbxColorDirective],
9794
+ hostDirectives: [DbxColorDirective],
9739
9795
  changeDetection: ChangeDetectionStrategy.OnPush,
9740
9796
  standalone: true
9741
9797
  }]
9742
- }], propDecorators: { avatarUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarUrl", required: false }] }], avatarName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarName", required: false }] }], avatarStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarStyle", required: false }] }], avatarIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIcon", required: false }] }], avatarHideOnError: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarHideOnError", required: false }] }] } });
9798
+ }], ctorParameters: () => [], propDecorators: { avatarUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarUrl", required: false }] }], avatarName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarName", required: false }] }], avatarStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarStyle", required: false }] }], avatarIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIcon", required: false }] }], avatarHideOnError: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarHideOnError", required: false }] }], avatarTile: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarTile", required: false }] }], avatarColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarColor", required: false }] }], avatarColorTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarColorTone", required: false }] }], avatarImageFit: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarImageFit", required: false }] }], avatarImageFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarImageFilter", required: false }] }], avatarIgnoreDefaultUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIgnoreDefaultUrl", required: false }] }] } });
9743
9799
 
9744
9800
  /**
9745
9801
  * Abstract configuration class for customizing the avatar view service defaults.
@@ -9856,6 +9912,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9856
9912
  * Merges individual input properties with the optional `context` input and delegates rendering
9857
9913
  * to the component resolved by {@link DbxAvatarViewService}. Supports size variants via CSS classes.
9858
9914
  *
9915
+ * Renders an image when one is available, then name initials on a curated color, then a Material icon.
9916
+ * With `[avatarTile]` the avatar takes the `<dbx-icon-tile>` presentation — every `--dbx-icon-tile-*`
9917
+ * customization applies and `[avatarColor]` / `[avatarColorTone]` paint the tile surface — which makes
9918
+ * it the right choice for a branded row (a connected app, an integration) rather than a person.
9919
+ *
9920
+ * @dbxWebComponent
9921
+ * @dbxWebSlug avatar
9922
+ * @dbxWebCategory layout
9923
+ * @dbxWebRelated icon-tile, dbx-color, avatar-view
9924
+ * @dbxWebMinimalExample ```html
9925
+ * <dbx-avatar [avatarUrl]="user.photoUrl"></dbx-avatar>
9926
+ * ```
9927
+ *
9859
9928
  * @example
9860
9929
  * ```html
9861
9930
  * <dbx-avatar [avatarUrl]="user.photoUrl" [avatarStyle]="'circle'" [avatarSize]="'large'"></dbx-avatar>
@@ -9863,6 +9932,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9863
9932
  *
9864
9933
  * @example
9865
9934
  * ```html
9935
+ * <!-- branded tile: logo image when there is one, Material icon otherwise -->
9936
+ * <dbx-avatar [avatarTile]="true" avatarStyle="square" [avatarUrl]="provider.logoUrl" avatarIcon="link" [avatarIgnoreDefaultUrl]="true" avatarColor="success" [avatarColorTone]="18"></dbx-avatar>
9937
+ * ```
9938
+ *
9939
+ * @example
9940
+ * ```html
9866
9941
  * <dbx-avatar [context]="avatarContext"></dbx-avatar>
9867
9942
  * ```
9868
9943
  */
@@ -9879,6 +9954,14 @@ class DbxAvatarComponent {
9879
9954
  avatarStyle = input(...(ngDevMode ? [undefined, { debugName: "avatarStyle" }] : /* istanbul ignore next */ []));
9880
9955
  avatarSize = input(undefined, ...(ngDevMode ? [{ debugName: "avatarSize" }] : /* istanbul ignore next */ []));
9881
9956
  avatarHideOnError = input(...(ngDevMode ? [undefined, { debugName: "avatarHideOnError" }] : /* istanbul ignore next */ []));
9957
+ avatarTile = input(...(ngDevMode ? [undefined, { debugName: "avatarTile" }] : /* istanbul ignore next */ []));
9958
+ avatarColor = input(...(ngDevMode ? [undefined, { debugName: "avatarColor" }] : /* istanbul ignore next */ []));
9959
+ avatarColorTone = input(...(ngDevMode ? [undefined, { debugName: "avatarColorTone" }] : /* istanbul ignore next */ []));
9960
+ avatarImageFit = input(...(ngDevMode ? [undefined, { debugName: "avatarImageFit" }] : /* istanbul ignore next */ []));
9961
+ avatarImageFilter = input(...(ngDevMode ? [undefined, { debugName: "avatarImageFilter" }] : /* istanbul ignore next */ []));
9962
+ avatarIgnoreDefaultUrl = input(...(ngDevMode ? [undefined, { debugName: "avatarIgnoreDefaultUrl" }] : /* istanbul ignore next */ []));
9963
+ // NOTE: every DbxAvatarContext field is enumerated here. A field added to the interface and to the
9964
+ // inputs above but forgotten here silently does nothing when bound as an input.
9882
9965
  contextSignal = computed(() => {
9883
9966
  const inputContext = this.context();
9884
9967
  const selector = this.avatarSelector() ?? inputContext?.selector;
@@ -9889,6 +9972,12 @@ class DbxAvatarComponent {
9889
9972
  const icon = this.avatarIcon() ?? inputContext?.icon;
9890
9973
  const style = this.avatarStyle() ?? inputContext?.style;
9891
9974
  const hideOnError = this.avatarHideOnError() ?? inputContext?.hideOnError;
9975
+ const tile = this.avatarTile() ?? inputContext?.tile;
9976
+ const color = this.avatarColor() ?? inputContext?.color;
9977
+ const colorTone = this.avatarColorTone() ?? inputContext?.colorTone;
9978
+ const imageFit = this.avatarImageFit() ?? inputContext?.imageFit;
9979
+ const imageFilter = this.avatarImageFilter() ?? inputContext?.imageFilter;
9980
+ const ignoreDefaultUrl = this.avatarIgnoreDefaultUrl() ?? inputContext?.ignoreDefaultUrl;
9892
9981
  return {
9893
9982
  selector,
9894
9983
  uid,
@@ -9897,7 +9986,13 @@ class DbxAvatarComponent {
9897
9986
  key,
9898
9987
  icon,
9899
9988
  style,
9900
- hideOnError
9989
+ hideOnError,
9990
+ tile,
9991
+ color,
9992
+ colorTone,
9993
+ imageFit,
9994
+ imageFilter,
9995
+ ignoreDefaultUrl
9901
9996
  };
9902
9997
  }, ...(ngDevMode ? [{ debugName: "contextSignal" }] : /* istanbul ignore next */ []));
9903
9998
  configSignal = computed(() => {
@@ -9918,7 +10013,7 @@ class DbxAvatarComponent {
9918
10013
  return config;
9919
10014
  }, ...(ngDevMode ? [{ debugName: "configSignal" }] : /* istanbul ignore next */ []));
9920
10015
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9921
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.11", type: DbxAvatarComponent, isStandalone: true, selector: "dbx-avatar", inputs: { context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, avatarSelector: { classPropertyName: "avatarSelector", publicName: "avatarSelector", isSignal: true, isRequired: false, transformFunction: null }, avatarUid: { classPropertyName: "avatarUid", publicName: "avatarUid", isSignal: true, isRequired: false, transformFunction: null }, avatarUrl: { classPropertyName: "avatarUrl", publicName: "avatarUrl", isSignal: true, isRequired: false, transformFunction: null }, avatarName: { classPropertyName: "avatarName", publicName: "avatarName", isSignal: true, isRequired: false, transformFunction: null }, avatarKey: { classPropertyName: "avatarKey", publicName: "avatarKey", isSignal: true, isRequired: false, transformFunction: null }, avatarIcon: { classPropertyName: "avatarIcon", publicName: "avatarIcon", isSignal: true, isRequired: false, transformFunction: null }, avatarStyle: { classPropertyName: "avatarStyle", publicName: "avatarStyle", isSignal: true, isRequired: false, transformFunction: null }, avatarSize: { classPropertyName: "avatarSize", publicName: "avatarSize", isSignal: true, isRequired: false, transformFunction: null }, avatarHideOnError: { classPropertyName: "avatarHideOnError", publicName: "avatarHideOnError", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.dbx-avatar-small": "avatarSize() === 'small'", "class.dbx-avatar-large": "avatarSize() === 'large'" } }, ngImport: i0, template: `
10016
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.11", type: DbxAvatarComponent, isStandalone: true, selector: "dbx-avatar", inputs: { context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, avatarSelector: { classPropertyName: "avatarSelector", publicName: "avatarSelector", isSignal: true, isRequired: false, transformFunction: null }, avatarUid: { classPropertyName: "avatarUid", publicName: "avatarUid", isSignal: true, isRequired: false, transformFunction: null }, avatarUrl: { classPropertyName: "avatarUrl", publicName: "avatarUrl", isSignal: true, isRequired: false, transformFunction: null }, avatarName: { classPropertyName: "avatarName", publicName: "avatarName", isSignal: true, isRequired: false, transformFunction: null }, avatarKey: { classPropertyName: "avatarKey", publicName: "avatarKey", isSignal: true, isRequired: false, transformFunction: null }, avatarIcon: { classPropertyName: "avatarIcon", publicName: "avatarIcon", isSignal: true, isRequired: false, transformFunction: null }, avatarStyle: { classPropertyName: "avatarStyle", publicName: "avatarStyle", isSignal: true, isRequired: false, transformFunction: null }, avatarSize: { classPropertyName: "avatarSize", publicName: "avatarSize", isSignal: true, isRequired: false, transformFunction: null }, avatarHideOnError: { classPropertyName: "avatarHideOnError", publicName: "avatarHideOnError", isSignal: true, isRequired: false, transformFunction: null }, avatarTile: { classPropertyName: "avatarTile", publicName: "avatarTile", isSignal: true, isRequired: false, transformFunction: null }, avatarColor: { classPropertyName: "avatarColor", publicName: "avatarColor", isSignal: true, isRequired: false, transformFunction: null }, avatarColorTone: { classPropertyName: "avatarColorTone", publicName: "avatarColorTone", isSignal: true, isRequired: false, transformFunction: null }, avatarImageFit: { classPropertyName: "avatarImageFit", publicName: "avatarImageFit", isSignal: true, isRequired: false, transformFunction: null }, avatarImageFilter: { classPropertyName: "avatarImageFilter", publicName: "avatarImageFilter", isSignal: true, isRequired: false, transformFunction: null }, avatarIgnoreDefaultUrl: { classPropertyName: "avatarIgnoreDefaultUrl", publicName: "avatarIgnoreDefaultUrl", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.dbx-avatar-small": "avatarSize() === 'small'", "class.dbx-avatar-large": "avatarSize() === 'large'" }, classAttribute: "dbx-avatar" }, ngImport: i0, template: `
9922
10017
  <dbx-injection [config]="configSignal()"></dbx-injection>
9923
10018
  `, isInline: true, dependencies: [{ kind: "component", type: DbxInjectionComponent, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: ["config", "template"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9924
10019
  }
@@ -9931,13 +10026,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9931
10026
  `,
9932
10027
  imports: [DbxInjectionComponent],
9933
10028
  host: {
10029
+ class: 'dbx-avatar',
9934
10030
  '[class.dbx-avatar-small]': `avatarSize() === 'small'`,
9935
10031
  '[class.dbx-avatar-large]': `avatarSize() === 'large'`
9936
10032
  },
9937
10033
  changeDetection: ChangeDetectionStrategy.OnPush,
9938
10034
  standalone: true
9939
10035
  }]
9940
- }], propDecorators: { context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], avatarSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSelector", required: false }] }], avatarUid: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarUid", required: false }] }], avatarUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarUrl", required: false }] }], avatarName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarName", required: false }] }], avatarKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarKey", required: false }] }], avatarIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIcon", required: false }] }], avatarStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarStyle", required: false }] }], avatarSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSize", required: false }] }], avatarHideOnError: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarHideOnError", required: false }] }] } });
10036
+ }], propDecorators: { context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], avatarSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSelector", required: false }] }], avatarUid: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarUid", required: false }] }], avatarUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarUrl", required: false }] }], avatarName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarName", required: false }] }], avatarKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarKey", required: false }] }], avatarIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIcon", required: false }] }], avatarStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarStyle", required: false }] }], avatarSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSize", required: false }] }], avatarHideOnError: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarHideOnError", required: false }] }], avatarTile: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarTile", required: false }] }], avatarColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarColor", required: false }] }], avatarColorTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarColorTone", required: false }] }], avatarImageFit: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarImageFit", required: false }] }], avatarImageFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarImageFilter", required: false }] }], avatarIgnoreDefaultUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIgnoreDefaultUrl", required: false }] }] } });
9941
10037
 
9942
10038
  /**
9943
10039
  * Renders a horizontal bar used to visually group or separate content.
@@ -15592,7 +15688,7 @@ class DbxStepBlockComponent {
15592
15688
  }
15593
15689
  <ng-content></ng-content>
15594
15690
  </div>
15595
- `, isInline: true, dependencies: [{ kind: "component", type: DbxIconTileComponent, selector: "dbx-icon-tile", inputs: ["icon", "round"] }, { kind: "directive", type: DbxColorDirective, selector: "[dbxColor]", inputs: ["dbxColor", "dbxColorTone"], outputs: ["dbxColorChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15691
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxIconTileComponent, selector: "dbx-icon-tile", inputs: ["icon", "round"] }, { kind: "directive", type: DbxColorDirective, selector: "[dbxColor]", inputs: ["dbxColor", "dbxColorTone"], outputs: ["dbxColorChange", "dbxColorToneChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15596
15692
  }
15597
15693
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxStepBlockComponent, decorators: [{
15598
15694
  type: Component,
@@ -16152,7 +16248,7 @@ class DbxNumberWithLimitComponent {
16152
16248
  }
16153
16249
  <span>{{ suffixSignal() }}</span>
16154
16250
  </span>
16155
- `, isInline: true, dependencies: [{ kind: "directive", type: DbxColorDirective, selector: "[dbxColor]", inputs: ["dbxColor", "dbxColorTone"], outputs: ["dbxColorChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16251
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxColorDirective, selector: "[dbxColor]", inputs: ["dbxColor", "dbxColorTone"], outputs: ["dbxColorChange", "dbxColorToneChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16156
16252
  }
16157
16253
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxNumberWithLimitComponent, decorators: [{
16158
16254
  type: Component,
@@ -18055,6 +18151,18 @@ const DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING = 'focus';
18055
18151
  * Error message used when an encrypted entry is projected to the `error` status under {@link DbxPdfMergeEncryptedHandling} `'error'` mode.
18056
18152
  */
18057
18153
  const DBX_PDF_MERGE_ENCRYPTED_ERROR_MESSAGE = 'Password-protected PDFs cannot be merged.';
18154
+ /**
18155
+ * Default value for {@link DbxPdfMergeEditorConfig.pageEditing}. Off, so the editor's default behavior is unchanged.
18156
+ */
18157
+ const DEFAULT_DBX_PDF_MERGE_PAGE_EDITING = false;
18158
+ /**
18159
+ * Default value for {@link DbxPdfMergeEditorConfig.sidecar}. Off, so merged output bytes are unchanged.
18160
+ */
18161
+ const DEFAULT_DBX_PDF_MERGE_SIDECAR = false;
18162
+ /**
18163
+ * Message shown on an encrypted entry's row while page editing is enabled. Encrypted documents cannot be opened by `pdf-lib`, so their pages cannot be listed or edited — the entry stays a single opaque row.
18164
+ */
18165
+ const DBX_PDF_MERGE_ENCRYPTED_NOT_EDITABLE_MESSAGE = 'Encrypted — pages cannot be edited.';
18058
18166
  /**
18059
18167
  * MIME types accepted by the PDF merge editor by default: PDF documents and PNG/JPEG images.
18060
18168
  */
@@ -18063,6 +18171,45 @@ const DEFAULT_PDF_MERGE_ACCEPT = [PDF_MIME_TYPE, PNG_MIME_TYPE, JPEG_MIME_TYPE];
18063
18171
  * MIME type emitted by the merged result blob.
18064
18172
  */
18065
18173
  const PDF_MERGE_RESULT_MIME_TYPE = PDF_MIME_TYPE;
18174
+ /**
18175
+ * Group key used for pages whose source entry has no slot id.
18176
+ */
18177
+ const DEFAULT_PDF_MERGE_PAGE_GROUP_KEY = '_';
18178
+ /**
18179
+ * Returns the group key a page reorders within — its entry's slot id, or {@link DEFAULT_PDF_MERGE_PAGE_GROUP_KEY} when the entry is unslotted.
18180
+ *
18181
+ * @param slotId - Slot id of the owning entry, if any.
18182
+ * @returns The group key.
18183
+ * @__NO_SIDE_EFFECTS__
18184
+ */
18185
+ function pdfMergePageGroupKeyForSlotId(slotId) {
18186
+ return slotId ?? DEFAULT_PDF_MERGE_PAGE_GROUP_KEY;
18187
+ }
18188
+ /**
18189
+ * Separator between the entry id and the page index inside a page id.
18190
+ */
18191
+ const PDF_MERGE_PAGE_ID_SEPARATOR = ':';
18192
+ /**
18193
+ * Builds the stable identifier for a page within the editor.
18194
+ *
18195
+ * @param entryId - Id of the owning {@link PdfMergeEntry}.
18196
+ * @param sourceIndex - Zero-based page index within that entry's source document.
18197
+ * @returns The page id.
18198
+ * @__NO_SIDE_EFFECTS__
18199
+ */
18200
+ function makePdfMergePageId(entryId, sourceIndex) {
18201
+ return `${entryId}${PDF_MERGE_PAGE_ID_SEPARATOR}${sourceIndex}`;
18202
+ }
18203
+ /**
18204
+ * Returns the id of the entry a page id belongs to. Used to prune page state when an entry leaves the list.
18205
+ *
18206
+ * @param pageId - Page id produced by {@link makePdfMergePageId}.
18207
+ * @returns The owning entry's id.
18208
+ * @__NO_SIDE_EFFECTS__
18209
+ */
18210
+ function entryIdForPdfMergePageId(pageId) {
18211
+ return pageId.slice(0, pageId.indexOf(PDF_MERGE_PAGE_ID_SEPARATOR));
18212
+ }
18066
18213
  /**
18067
18214
  * Injection token for a workspace-wide default {@link DbxPdfMergeEditorConfig}. Use {@link provideDbxPdfMergeEditorConfig} to register a value.
18068
18215
  */
@@ -18090,6 +18237,233 @@ function provideDbxPdfMergeEditorPreserveEntriesOnSlotDestroy(preserve = true) {
18090
18237
  return { provide: DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY, useValue: preserve };
18091
18238
  }
18092
18239
 
18240
+ /**
18241
+ * Name of the JSON manifest attached to a merged PDF that records which pages came from which slot.
18242
+ */
18243
+ const PDF_MERGE_SIDECAR_FILE_NAME = 'dbx-pdf-merge.json';
18244
+ /**
18245
+ * Key set on each output page's dictionary holding that page's {@link PdfMergeSidecarPage.tag}.
18246
+ *
18247
+ * Custom page-dictionary keys travel with the page: they survive `copyPages` (including subsetting and reordering), a save/load round trip, and further edits. That is what makes tags — rather than the manifest's recorded indices — the authoritative record of which document a page belongs to.
18248
+ */
18249
+ const PDF_MERGE_PAGE_TAG_KEY = 'DbxPageTag';
18250
+ /**
18251
+ * Schema version of the emitted {@link PdfMergeSidecar}.
18252
+ */
18253
+ const PDF_MERGE_SIDECAR_VERSION = 1;
18254
+ /**
18255
+ * Description recorded on the attached manifest.
18256
+ */
18257
+ const PDF_MERGE_SIDECAR_DESCRIPTION = 'dbx-pdf-merge page manifest';
18258
+ /**
18259
+ * Builds the tag written onto an output page.
18260
+ *
18261
+ * @param slotId - Slot the page's source entry belongs to, or `null` when unslotted.
18262
+ * @param ordinal - Zero-based position of the page within its own slot's pages.
18263
+ * @returns A tag unique within the output document.
18264
+ * @__NO_SIDE_EFFECTS__
18265
+ */
18266
+ function makePdfMergeSidecarPageTag(slotId, ordinal) {
18267
+ return `${slotId ?? '_'}-${ordinal}`;
18268
+ }
18269
+ /**
18270
+ * Writes a page's tag onto its page dictionary so the association survives later reordering.
18271
+ *
18272
+ * @param page - Page in the output document.
18273
+ * @param tag - Tag to record.
18274
+ */
18275
+ function writePdfMergePageTag(page, tag) {
18276
+ page.node.set(PDFName.of(PDF_MERGE_PAGE_TAG_KEY), PDFString.of(tag));
18277
+ }
18278
+ /**
18279
+ * Reads the tag previously written onto a page, if any.
18280
+ *
18281
+ * Uses `get` plus an instance check rather than a typed `lookup`, because `PDFDict.lookup(key, PDFString)` throws when the key is absent — which is the normal case for any PDF that did not come out of this editor.
18282
+ *
18283
+ * @param page - Page to inspect.
18284
+ * @returns The tag, or `null` when the page carries none.
18285
+ */
18286
+ function readPdfMergePageTag(page) {
18287
+ const value = page.node.get(PDFName.of(PDF_MERGE_PAGE_TAG_KEY));
18288
+ let tag;
18289
+ if (value instanceof PDFString || value instanceof PDFHexString) {
18290
+ tag = value.decodeText();
18291
+ }
18292
+ else {
18293
+ tag = null;
18294
+ }
18295
+ return tag;
18296
+ }
18297
+ /**
18298
+ * Attaches a manifest to the output document as an embedded JSON file.
18299
+ *
18300
+ * @param document - Output document being saved.
18301
+ * @param sidecar - Manifest to embed.
18302
+ */
18303
+ async function attachPdfMergeSidecar(document, sidecar) {
18304
+ // Re-wrap with this module's Uint8Array: pdf-lib validates its input with `instanceof`, and a
18305
+ // TextEncoder supplied by another realm (Node's, under jsdom) produces an array that fails that
18306
+ // check even though it is a genuine Uint8Array.
18307
+ const bytes = new Uint8Array(new TextEncoder().encode(JSON.stringify(sidecar)));
18308
+ await document.attach(bytes, PDF_MERGE_SIDECAR_FILE_NAME, {
18309
+ mimeType: JSON_MIME_TYPE,
18310
+ description: PDF_MERGE_SIDECAR_DESCRIPTION
18311
+ });
18312
+ }
18313
+ /**
18314
+ * Builds a {@link PdfMergeSidecar} from the pages written to an output document.
18315
+ *
18316
+ * @param pages - Page records in output order.
18317
+ * @returns The manifest, with one document per distinct slot in first-appearance order.
18318
+ * @__NO_SIDE_EFFECTS__
18319
+ */
18320
+ function makePdfMergeSidecar(pages) {
18321
+ const tagsBySlot = new Map();
18322
+ pages.forEach((page) => {
18323
+ const existing = tagsBySlot.get(page.slotId);
18324
+ if (existing == null) {
18325
+ tagsBySlot.set(page.slotId, [page.tag]);
18326
+ }
18327
+ else {
18328
+ existing.push(page.tag);
18329
+ }
18330
+ });
18331
+ const documents = Array.from(tagsBySlot.entries()).map(([slotId, pageTags]) => ({ slotId, pageTags }));
18332
+ return {
18333
+ version: PDF_MERGE_SIDECAR_VERSION,
18334
+ createdAt: new Date().toISOString(),
18335
+ documents,
18336
+ pages
18337
+ };
18338
+ }
18339
+ async function bytesForSidecarReadInput(input) {
18340
+ return input instanceof Blob ? input.arrayBuffer() : input;
18341
+ }
18342
+ function parseSidecarBytes(data) {
18343
+ let sidecar;
18344
+ try {
18345
+ const parsed = JSON.parse(new TextDecoder().decode(data));
18346
+ sidecar = Array.isArray(parsed?.documents) && Array.isArray(parsed?.pages) ? parsed : null;
18347
+ }
18348
+ catch {
18349
+ sidecar = null;
18350
+ }
18351
+ return sidecar;
18352
+ }
18353
+ /**
18354
+ * Reads the manifest embedded in a merged PDF and resolves each recorded page to its current position in that file.
18355
+ *
18356
+ * Resolution prefers each page's own tag, which follows the page through reordering. The manifest's {@link PdfMergeSidecarPage.outputIndex} is used only as a fallback for pages whose tag has been stripped, and any index that no longer exists is reported through {@link PdfMergeSidecarReadResult.missingTags} instead.
18357
+ *
18358
+ * @param input - The merged PDF's bytes.
18359
+ * @returns The manifest and its resolved documents, or `null` when the file carries no manifest, is unreadable, or is encrypted.
18360
+ */
18361
+ async function readPdfMergeSidecar(input) {
18362
+ let result;
18363
+ try {
18364
+ const bytes = await bytesForSidecarReadInput(input);
18365
+ const document = await PDFDocument.load(bytes);
18366
+ result = resolvePdfMergeSidecar(document);
18367
+ }
18368
+ catch {
18369
+ result = null;
18370
+ }
18371
+ return result;
18372
+ }
18373
+ /**
18374
+ * Resolves the manifest of an already-loaded document. Split out from {@link readPdfMergeSidecar} so {@link splitPdfMergeSidecarDocuments} can reuse the resolution without parsing the same bytes twice.
18375
+ *
18376
+ * @param document - Loaded merged document.
18377
+ * @returns The resolved manifest, or `null` when the document carries none.
18378
+ */
18379
+ function resolvePdfMergeSidecar(document) {
18380
+ let result;
18381
+ {
18382
+ const attachment = document.getAttachments().find((x) => x.name === PDF_MERGE_SIDECAR_FILE_NAME);
18383
+ const sidecar = attachment == null ? null : parseSidecarBytes(attachment.data);
18384
+ if (sidecar == null) {
18385
+ result = null;
18386
+ }
18387
+ else {
18388
+ const pageCount = document.getPageCount();
18389
+ const indexByTag = new Map();
18390
+ let untaggedPageCount = 0;
18391
+ document.getPages().forEach((page, index) => {
18392
+ const tag = readPdfMergePageTag(page);
18393
+ if (tag == null) {
18394
+ untaggedPageCount += 1;
18395
+ }
18396
+ else {
18397
+ indexByTag.set(tag, index);
18398
+ }
18399
+ });
18400
+ const recordByTag = new Map(sidecar.pages.map((page) => [page.tag, page]));
18401
+ const missingTags = [];
18402
+ const documents = sidecar.documents.map((sidecarDocument) => {
18403
+ const pages = [];
18404
+ sidecarDocument.pageTags.forEach((tag) => {
18405
+ const record = recordByTag.get(tag);
18406
+ const taggedIndex = indexByTag.get(tag);
18407
+ if (record == null) {
18408
+ missingTags.push(tag);
18409
+ }
18410
+ else if (taggedIndex != null) {
18411
+ pages.push({ ...record, pageIndex: taggedIndex, resolvedByTag: true });
18412
+ }
18413
+ else if (record.outputIndex >= 0 && record.outputIndex < pageCount) {
18414
+ pages.push({ ...record, pageIndex: record.outputIndex, resolvedByTag: false });
18415
+ }
18416
+ else {
18417
+ missingTags.push(tag);
18418
+ }
18419
+ });
18420
+ return { slotId: sidecarDocument.slotId, pages };
18421
+ });
18422
+ result = { sidecar, documents, missingTags, untaggedPageCount };
18423
+ }
18424
+ }
18425
+ return result;
18426
+ }
18427
+ /**
18428
+ * Splits a previously-merged PDF back into one file per document, using the embedded manifest to decide which pages belong to which slot.
18429
+ *
18430
+ * This is the inverse of a merge with `sidecar: true`, and is what lets a completed document be re-imported into the editor with its slots repopulated instead of arriving as one opaque file. Page order and any rotation baked in at merge time are preserved, and each document's pages are resolved by tag so a document that was reordered after export still splits correctly.
18431
+ *
18432
+ * @param input - Bytes of a merged PDF.
18433
+ * @returns One file per document plus the manifest, or `null` when the input carries no manifest, is unreadable, or is encrypted.
18434
+ */
18435
+ async function splitPdfMergeSidecarDocuments(input) {
18436
+ let result;
18437
+ try {
18438
+ const bytes = await bytesForSidecarReadInput(input);
18439
+ const source = await PDFDocument.load(bytes);
18440
+ const read = resolvePdfMergeSidecar(source);
18441
+ if (read == null) {
18442
+ result = null;
18443
+ }
18444
+ else {
18445
+ const documents = [];
18446
+ for (const document of read.documents) {
18447
+ if (document.pages.length > 0) {
18448
+ const target = await PDFDocument.create();
18449
+ const copied = await target.copyPages(source, document.pages.map((page) => page.pageIndex));
18450
+ copied.forEach((page) => target.addPage(page));
18451
+ const outputBytes = await target.save();
18452
+ const name = document.pages[0].sourceName || `${document.slotId ?? 'document'}.pdf`;
18453
+ const file = new File([outputBytes], name, { type: PDF_MIME_TYPE });
18454
+ documents.push({ slotId: document.slotId, file, pageCount: document.pages.length });
18455
+ }
18456
+ }
18457
+ result = { sidecar: read.sidecar, documents, missingTags: read.missingTags, untaggedPageCount: read.untaggedPageCount };
18458
+ }
18459
+ }
18460
+ catch {
18461
+ result = null;
18462
+ }
18463
+ return result;
18464
+ }
18465
+
18466
+ const FULL_ROTATION_DEGREES = 360;
18093
18467
  const TEXT_DECODER = new TextDecoder('latin1');
18094
18468
  const FORMAT_KILOBYTE = 1024;
18095
18469
  const FORMAT_MEGABYTE = FORMAT_KILOBYTE * 1024;
@@ -18113,6 +18487,33 @@ function formatPdfMergeEntrySize(size) {
18113
18487
  }
18114
18488
  return result;
18115
18489
  }
18490
+ const DEFAULT_BLOB_FILE_NAMES = {
18491
+ [PDF_MIME_TYPE]: 'document.pdf',
18492
+ [PNG_MIME_TYPE]: 'image.png',
18493
+ [JPEG_MIME_TYPE]: 'image.jpg'
18494
+ };
18495
+ const DEFAULT_BLOB_FILE_NAME = DEFAULT_BLOB_FILE_NAMES[PDF_MIME_TYPE];
18496
+ /**
18497
+ * Normalizes a {@link Blob} into a {@link File} so it can become a {@link PdfMergeEntry}, which needs a name to display.
18498
+ *
18499
+ * A {@link File} passes through untouched unless an explicit `fileName` overrides it. A bare {@link Blob} is named from `fileName`, else from its MIME type (`document.pdf` / `image.png` / `image.jpg`), else `document.pdf` — the name only has to be plausible and correctly suffixed, since {@link classifyPdfMergeFile} falls back to the extension when a blob carries no type.
18500
+ *
18501
+ * @param input - Blob or File to normalize.
18502
+ * @param fileName - Optional name, overriding a File's own name and naming a bare Blob.
18503
+ * @returns A File carrying the resolved name.
18504
+ */
18505
+ function asPdfMergeFile(input, fileName) {
18506
+ const mimeType = (input.type ?? '').toLowerCase();
18507
+ let file;
18508
+ if (input instanceof File && fileName == null) {
18509
+ file = input;
18510
+ }
18511
+ else {
18512
+ const name = fileName ?? DEFAULT_BLOB_FILE_NAMES[mimeType] ?? DEFAULT_BLOB_FILE_NAME;
18513
+ file = new File([input], name, { type: input.type || mimeTypeForFileExtension(slashPathDetails(name.toLowerCase()).typedFileExtension) || PDF_MIME_TYPE });
18514
+ }
18515
+ return file;
18516
+ }
18116
18517
  /**
18117
18518
  * Returns the {@link PdfMergeEntryKind} for a file based on its MIME type, with a small fallback to file-extension matching when the browser provided no MIME type.
18118
18519
  *
@@ -18267,30 +18668,305 @@ async function validatePdfMergeEntry(entry) {
18267
18668
  }
18268
18669
  return result;
18269
18670
  }
18270
- async function appendPdfPages(target, entry) {
18271
- const bytes = await entry.file.arrayBuffer();
18272
- const source = await PDFDocument.load(bytes);
18273
- const pages = await target.copyPages(source, source.getPageIndices());
18274
- pages.forEach((page) => target.addPage(page));
18671
+ /**
18672
+ * Normalizes an arbitrary degree value into the `[0, 360)` range.
18673
+ *
18674
+ * @param angle - Angle in degrees, possibly negative or over a full turn.
18675
+ * @returns The equivalent angle in `[0, 360)`.
18676
+ * @__NO_SIDE_EFFECTS__
18677
+ */
18678
+ function normalizeRotationDegrees(angle) {
18679
+ return ((angle % FULL_ROTATION_DEGREES) + FULL_ROTATION_DEGREES) % FULL_ROTATION_DEGREES;
18680
+ }
18681
+ /**
18682
+ * Reads per-page metadata from an entry's source document so the editor can list its pages individually.
18683
+ *
18684
+ * Only called while page editing is enabled — the normal upload path never parses the document, it only scans for the PDF header and markers (see {@link validatePdfMergeEntry}).
18685
+ *
18686
+ * @param entry - Entry to inspect.
18687
+ * @returns One {@link PdfMergePageMeta} per source page, or `null` when the entry is encrypted or cannot be parsed. Image entries report a single synthetic page.
18688
+ */
18689
+ async function readPdfMergeEntryPageMetas(entry) {
18690
+ let metas;
18691
+ if (entry.encrypted) {
18692
+ // pdf-lib cannot open an encrypted document at all, so its pages can never be listed or edited.
18693
+ metas = null;
18694
+ }
18695
+ else if (entry.kind === 'image') {
18696
+ const dimensions = entry.original.dimensions;
18697
+ metas = [{ sourceIndex: 0, width: dimensions?.width ?? 0, height: dimensions?.height ?? 0, sourceRotation: 0 }];
18698
+ }
18699
+ else {
18700
+ try {
18701
+ const bytes = await entry.file.arrayBuffer();
18702
+ const source = await PDFDocument.load(bytes);
18703
+ metas = source.getPages().map((page, sourceIndex) => {
18704
+ const { width, height } = page.getSize();
18705
+ return { sourceIndex, width, height, sourceRotation: normalizeRotationDegrees(page.getRotation().angle) };
18706
+ });
18707
+ }
18708
+ catch {
18709
+ metas = null;
18710
+ }
18711
+ }
18712
+ return metas;
18713
+ }
18714
+ /**
18715
+ * Reconstructs editor entries from a PDF previously exported with an embedded manifest.
18716
+ *
18717
+ * Each document recorded in the manifest becomes one entry carrying its original slot id, so dropping the result into the store repopulates the same slots the file was built from. This is the re-import half of the sidecar: without it a completed document can only come back as a single opaque file.
18718
+ *
18719
+ * @param input - Bytes of a merged PDF, typically the file the user just chose.
18720
+ * @param config - Optional entry-building config (id factory override, and the sidecar-less fallback).
18721
+ * @returns The reconstructed entries and manifest, or a reason when the file cannot be imported.
18722
+ */
18723
+ async function buildPdfMergeEntriesFromSidecar(input, config) {
18724
+ const split = await splitPdfMergeSidecarDocuments(input);
18725
+ let result;
18726
+ if (split == null) {
18727
+ // A readable PDF without a manifest and an unreadable one are distinguished so the UI can
18728
+ // explain the difference — "not exported from here" reads very differently from "corrupt".
18729
+ const readable = await isReadablePdf(input);
18730
+ if (!readable) {
18731
+ result = { error: 'unreadable' };
18732
+ }
18733
+ else if (config?.allowWithoutSidecar) {
18734
+ // No manifest to attribute pages with, so the whole file becomes one unslotted entry. An
18735
+ // `unreadable` file never reaches here — the fallback widens which readable PDFs are
18736
+ // accepted, it does not accept broken ones.
18737
+ const entry = buildPdfMergeEntrySync(asPdfMergeFile(input, config?.fileName), { ...config, slotId: null });
18738
+ result = entry == null ? { error: 'unreadable' } : { entries: [entry], sidecar: null, fromSidecar: false, slotIds: [null], missingTags: [], untaggedPageCount: 0 };
18739
+ }
18740
+ else {
18741
+ result = { error: 'no_sidecar' };
18742
+ }
18743
+ }
18744
+ else if (split.documents.length === 0) {
18745
+ result = { error: 'no_documents' };
18746
+ }
18747
+ else {
18748
+ const entries = split.documents.map((document) => buildPdfMergeEntrySync(document.file, { ...config, slotId: document.slotId })).filter((entry) => entry != null);
18749
+ result = {
18750
+ entries,
18751
+ sidecar: split.sidecar,
18752
+ fromSidecar: true,
18753
+ slotIds: split.documents.map((document) => document.slotId),
18754
+ missingTags: split.missingTags,
18755
+ untaggedPageCount: split.untaggedPageCount
18756
+ };
18757
+ }
18758
+ return result;
18759
+ }
18760
+ async function isReadablePdf(input) {
18761
+ let readable;
18762
+ try {
18763
+ await PDFDocument.load(await input.arrayBuffer());
18764
+ readable = true;
18765
+ }
18766
+ catch {
18767
+ readable = false;
18768
+ }
18769
+ return readable;
18770
+ }
18771
+ function applyPdfMergePageGroupOrder(natural, orderedIds) {
18772
+ let ordered;
18773
+ if (orderedIds == null || orderedIds.length === 0) {
18774
+ ordered = [...natural];
18775
+ }
18776
+ else {
18777
+ const byId = new Map(natural.map((page) => [page.id, page]));
18778
+ const taken = new Set();
18779
+ const result = [];
18780
+ // Stored ids that still resolve keep their stored order; anything else is ignored.
18781
+ orderedIds.forEach((id) => {
18782
+ const page = byId.get(id);
18783
+ if (page != null && !taken.has(id)) {
18784
+ result.push(page);
18785
+ taken.add(id);
18786
+ }
18787
+ });
18788
+ // Pages the stored order does not mention are new since it was written — append them in natural order.
18789
+ natural.forEach((page) => {
18790
+ if (!taken.has(page.id)) {
18791
+ result.push(page);
18792
+ }
18793
+ });
18794
+ ordered = result;
18795
+ }
18796
+ return ordered;
18797
+ }
18798
+ /**
18799
+ * Builds the editor's page plan from the current entries plus the user's sparse edits.
18800
+ *
18801
+ * The result is self-healing: stored ordering ids that no longer resolve are dropped, pages the stored ordering does not mention are appended in natural order, and overrides for departed pages are ignored. That means entries can be added and removed without any page-level bookkeeping.
18802
+ *
18803
+ * Entries whose metadata is `null` contribute no pages — they are encrypted (which `pdf-lib` cannot open) or unparseable. Callers should surface those separately rather than letting them disappear silently.
18804
+ *
18805
+ * @param input - Entries, hydrated metadata, and the stored edits.
18806
+ * @returns The ordered page plan across every group.
18807
+ * @__NO_SIDE_EFFECTS__
18808
+ */
18809
+ function buildPdfMergePagePlan(input) {
18810
+ const { entries, pageMetas, pageOverrides, pageOrder } = input;
18811
+ const eligible = entries.filter((entry) => entry.status === 'ready' && !entry.ignored && pageMetas[entry.id] != null);
18812
+ const groups = makeValuesGroupMap(eligible, (entry) => pdfMergePageGroupKeyForSlotId(entry.slotId));
18813
+ const plan = [];
18814
+ groups.forEach((groupEntries, groupKey) => {
18815
+ const natural = [];
18816
+ groupEntries.forEach((entry) => {
18817
+ const metas = pageMetas[entry.id];
18818
+ metas.forEach((meta) => {
18819
+ const id = makePdfMergePageId(entry.id, meta.sourceIndex);
18820
+ const override = pageOverrides[id];
18821
+ natural.push({
18822
+ id,
18823
+ entryId: entry.id,
18824
+ slotId: entry.slotId ?? null,
18825
+ groupKey: groupKey,
18826
+ sourceName: entry.name,
18827
+ kind: entry.kind,
18828
+ sourceIndex: meta.sourceIndex,
18829
+ sourcePageCount: metas.length,
18830
+ meta,
18831
+ rotation: override?.rotation ?? 0,
18832
+ removed: override?.removed ?? false
18833
+ });
18834
+ });
18835
+ });
18836
+ applyPdfMergePageGroupOrder(natural, pageOrder[groupKey]).forEach((page) => plan.push(page));
18837
+ });
18838
+ return plan;
18839
+ }
18840
+ /**
18841
+ * Returns a loader that parses each entry's source document at most once per merge. A page plan routinely pulls several non-adjacent pages from the same file, so without this the same bytes would be parsed repeatedly.
18842
+ *
18843
+ * @returns Loader that resolves an entry to its parsed source document.
18844
+ */
18845
+ function makePdfSourceDocumentLoader() {
18846
+ const cache = new Map();
18847
+ return (entry) => {
18848
+ let loaded = cache.get(entry.id);
18849
+ if (loaded == null) {
18850
+ loaded = entry.file.arrayBuffer().then((bytes) => PDFDocument.load(bytes));
18851
+ cache.set(entry.id, loaded);
18852
+ }
18853
+ return loaded;
18854
+ };
18275
18855
  }
18276
- async function appendImagePage(target, entry) {
18856
+ async function appendImagePage(target, entry, rotation) {
18277
18857
  const bytes = await entry.file.arrayBuffer();
18278
18858
  const isPng = entry.mimeType === PNG_MIME_TYPE || entry.name.toLowerCase().endsWith('.png');
18279
18859
  const image = isPng ? await target.embedPng(bytes) : await target.embedJpg(bytes);
18280
18860
  const page = target.addPage([image.width, image.height]);
18281
18861
  page.drawImage(image, { x: 0, y: 0, width: image.width, height: image.height });
18862
+ if (rotation !== 0) {
18863
+ page.setRotation(degrees(rotation));
18864
+ }
18865
+ return { entry, sourceIndex: 0, rotation };
18866
+ }
18867
+ /**
18868
+ * Appends every page of every entry, in entry order — the behavior used when no page plan is supplied.
18869
+ *
18870
+ * @param input - Target document, entries, and source loader.
18871
+ * @returns One record per appended page, aligned with the target's pages.
18872
+ */
18873
+ async function appendAllEntryPages(input) {
18874
+ const { target, entries, loadSource } = input;
18875
+ const records = [];
18876
+ for (const entry of entries) {
18877
+ if (entry.kind === 'pdf') {
18878
+ const source = await loadSource(entry);
18879
+ const indices = source.getPageIndices();
18880
+ const copied = await target.copyPages(source, indices);
18881
+ copied.forEach((page, index) => {
18882
+ target.addPage(page);
18883
+ records.push({ entry, sourceIndex: indices[index], rotation: normalizeRotationDegrees(page.getRotation().angle) });
18884
+ });
18885
+ }
18886
+ else {
18887
+ records.push(await appendImagePage(target, entry, 0));
18888
+ }
18889
+ }
18890
+ return records;
18891
+ }
18892
+ /**
18893
+ * Appends pages according to an explicit plan — honoring subsetting, reordering, and per-page rotation.
18894
+ *
18895
+ * Consecutive pages from the same entry are batched into a single `copyPages` call. `copyPages` returns pages in the order of the indices it is given, which is what lets subsetting and reordering happen in one operation.
18896
+ *
18897
+ * @param input - Target document, entries, page plan, and source loader.
18898
+ * @returns One record per appended page, aligned with the target's pages.
18899
+ */
18900
+ async function appendPlannedPages(input) {
18901
+ const { target, entries, plan, loadSource } = input;
18902
+ const entriesById = new Map(entries.map((entry) => [entry.id, entry]));
18903
+ const visible = plan.filter((page) => !page.removed && entriesById.has(page.entryId));
18904
+ const records = [];
18905
+ let index = 0;
18906
+ while (index < visible.length) {
18907
+ const first = visible[index];
18908
+ const entry = entriesById.get(first.entryId);
18909
+ let end = index + 1;
18910
+ while (end < visible.length && visible[end].entryId === first.entryId) {
18911
+ end += 1;
18912
+ }
18913
+ const run = visible.slice(index, end);
18914
+ if (entry.kind === 'image') {
18915
+ for (const planned of run) {
18916
+ records.push(await appendImagePage(target, entry, normalizeRotationDegrees(planned.rotation)));
18917
+ }
18918
+ }
18919
+ else {
18920
+ const source = await loadSource(entry);
18921
+ const copied = await target.copyPages(source, run.map((planned) => planned.sourceIndex));
18922
+ copied.forEach((page, runIndex) => {
18923
+ const planned = run[runIndex];
18924
+ // Compose rather than replace: a scan already stored at 90° that the user rotates by 90° must land at 180°.
18925
+ const rotation = normalizeRotationDegrees(planned.meta.sourceRotation + planned.rotation);
18926
+ target.addPage(page);
18927
+ page.setRotation(degrees(rotation));
18928
+ records.push({ entry, sourceIndex: planned.sourceIndex, rotation });
18929
+ });
18930
+ }
18931
+ index = end;
18932
+ }
18933
+ return records;
18934
+ }
18935
+ /**
18936
+ * Tags each output page and attaches the manifest describing which pages belong to which slot.
18937
+ *
18938
+ * @param target - Document being assembled, whose pages align with `records`.
18939
+ * @param records - One record per appended page, in output order.
18940
+ */
18941
+ async function writePdfMergeSidecarForRecords(target, records) {
18942
+ const ordinalBySlotKey = new Map();
18943
+ const outputPages = target.getPages();
18944
+ const sidecarPages = records.map((record, outputIndex) => {
18945
+ // Normalize to null so the value survives JSON.stringify, which drops undefined.
18946
+ const slotId = record.entry.slotId ?? null;
18947
+ const slotKey = slotId ?? '';
18948
+ const ordinal = ordinalBySlotKey.get(slotKey) ?? 0;
18949
+ const tag = makePdfMergeSidecarPageTag(slotId, ordinal);
18950
+ ordinalBySlotKey.set(slotKey, ordinal + 1);
18951
+ writePdfMergePageTag(outputPages[outputIndex], tag);
18952
+ return { tag, slotId, sourceName: record.entry.name, sourceIndex: record.sourceIndex, rotation: record.rotation, outputIndex };
18953
+ });
18954
+ await attachPdfMergeSidecar(target, makePdfMergeSidecar(sidecarPages));
18282
18955
  }
18283
18956
  /**
18284
18957
  * Merges every `ready` entry in the provided array order into a single PDF and returns it as a `Blob`. PDF entries contribute their full set of pages in order; image entries contribute one page sized to the image.
18285
18958
  *
18286
- * Special case: when the only `ready` entry is a single encrypted PDF, returns a passthrough `Blob` of its original bytes — `pdf-lib` cannot read encrypted PDFs even with `ignoreEncryption: true`, and downstream upload flows still need a usable blob.
18959
+ * Supplying {@link MergePdfMergeEntriesOptions.pages} replaces that default with an explicit page plan, so pages can be subsetted, reordered, and rotated. Supplying {@link MergePdfMergeEntriesOptions.sidecar} additionally embeds a manifest of which pages came from which slot.
18287
18960
  *
18288
- * Throws if no `ready` entries are provided, or if multiple encrypted entries are passed in (the editor only routes here under focus mode where it has narrowed to one).
18961
+ * Special case: when the only `ready` entry is a single encrypted PDF, returns a passthrough `Blob` of its original bytes — `pdf-lib` cannot read encrypted PDFs even with `ignoreEncryption: true`, and downstream upload flows still need a usable blob. Neither the page plan nor the sidecar applies on that path, since the document cannot be opened.
18962
+ *
18963
+ * Throws if no `ready` entries are provided, if multiple encrypted entries are passed in (the editor only routes here under focus mode where it has narrowed to one), or if the page plan leaves no pages to emit.
18289
18964
  *
18290
18965
  * @param entries - Ordered entries to merge.
18966
+ * @param options - Optional page plan and sidecar switch.
18291
18967
  * @returns A Blob with `application/pdf` MIME type.
18292
18968
  */
18293
- async function mergePdfMergeEntries(entries) {
18969
+ async function mergePdfMergeEntries(entries, options) {
18294
18970
  const ready = entries.filter((entry) => entry.status === 'ready');
18295
18971
  let result;
18296
18972
  if (ready.length === 0) {
@@ -18305,13 +18981,14 @@ async function mergePdfMergeEntries(entries) {
18305
18981
  }
18306
18982
  else {
18307
18983
  const target = await PDFDocument.create();
18308
- for (const entry of ready) {
18309
- if (entry.kind === 'pdf') {
18310
- await appendPdfPages(target, entry);
18311
- }
18312
- else {
18313
- await appendImagePage(target, entry);
18314
- }
18984
+ const loadSource = makePdfSourceDocumentLoader();
18985
+ const plan = options?.pages;
18986
+ const records = plan == null ? await appendAllEntryPages({ target, entries: ready, loadSource }) : await appendPlannedPages({ target, entries: ready, plan, loadSource });
18987
+ if (records.length === 0) {
18988
+ throw new Error('No pages to merge.');
18989
+ }
18990
+ if (options?.sidecar) {
18991
+ await writePdfMergeSidecarForRecords(target, records);
18315
18992
  }
18316
18993
  const bytes = await target.save();
18317
18994
  result = new Blob([bytes], { type: PDF_MERGE_RESULT_MIME_TYPE });
@@ -18320,11 +18997,39 @@ async function mergePdfMergeEntries(entries) {
18320
18997
  }
18321
18998
 
18322
18999
  /**
18323
- * Initial state used by {@link DbxPdfMergeEditorStore} — no entries.
19000
+ * Initial state used by {@link DbxPdfMergeEditorStore} — no entries and no page edits.
18324
19001
  */
18325
19002
  const DBX_PDF_MERGE_EDITOR_INITIAL_STATE = {
18326
- rawEntries: []
19003
+ rawEntries: [],
19004
+ pageOverrides: {},
19005
+ pageOrder: {}
18327
19006
  };
19007
+ /**
19008
+ * Drops page overrides and stored orderings that belong to entries no longer in the list.
19009
+ *
19010
+ * The derived plan already ignores stale ids, so this is hygiene rather than correctness — it keeps the two records from growing without bound as the user adds and removes files.
19011
+ *
19012
+ * @param state - Current state.
19013
+ * @param liveEntryIds - Ids of the entries that remain.
19014
+ * @returns The pruned page-state slice.
19015
+ * @__NO_SIDE_EFFECTS__
19016
+ */
19017
+ function prunePdfMergePageState(state, liveEntryIds) {
19018
+ const pageOverrides = {};
19019
+ const pageOrder = {};
19020
+ Object.entries(state.pageOverrides).forEach(([pageId, override]) => {
19021
+ if (liveEntryIds.has(entryIdForPdfMergePageId(pageId))) {
19022
+ pageOverrides[pageId] = override;
19023
+ }
19024
+ });
19025
+ Object.entries(state.pageOrder).forEach(([groupKey, pageIds]) => {
19026
+ const kept = pageIds.filter((pageId) => liveEntryIds.has(entryIdForPdfMergePageId(pageId)));
19027
+ if (kept.length > 0) {
19028
+ pageOrder[groupKey] = kept;
19029
+ }
19030
+ });
19031
+ return { pageOverrides, pageOrder };
19032
+ }
18328
19033
  /**
18329
19034
  * Component-scoped {@link ComponentStore} that owns the list of files staged for merging in the PDF merge editor. Each {@link PdfMergeEntry} carries its own validation promise from the moment it is built; {@link entries$} composes those promises into a live stream — emitting the entry first in `validating` state and then again as each promise resolves to `ready` or `error`. {@link mergeOutput$} emits the merged PDF blob once every entry has finished validating, at least one is `ready`, and the registered validator delegate (if any) reports `true`.
18330
19035
  */
@@ -18333,7 +19038,19 @@ class DbxPdfMergeEditorStore extends ComponentStore {
18333
19038
  _outputSizeLimit$ = new BehaviorSubject(undefined);
18334
19039
  _imageCompression$ = new BehaviorSubject(undefined);
18335
19040
  _encryptedHandling$ = new BehaviorSubject(undefined);
18336
- constructor() {
19041
+ _pageEditing$ = new BehaviorSubject(undefined);
19042
+ _sidecar$ = new BehaviorSubject(undefined);
19043
+ /**
19044
+ * Per-entry page metadata, parsed at most once per entry. Keyed by entry id; a resolved `null` means the entry could not be expanded. Only populated while page editing is enabled.
19045
+ */
19046
+ _pageMetaCache = new Map();
19047
+ /**
19048
+ * Mount count per registered slot id. See {@link registerSlotId} for why this is a count rather than a set.
19049
+ */
19050
+ _slotIdCounts = new Map();
19051
+ _registeredSlotIds$ = new BehaviorSubject([]);
19052
+ _importState$ = new BehaviorSubject(undefined);
19053
+ constructor() {
18337
19054
  super(DBX_PDF_MERGE_EDITOR_INITIAL_STATE);
18338
19055
  }
18339
19056
  // MARK: Selectors
@@ -18400,6 +19117,99 @@ class DbxPdfMergeEditorStore extends ComponentStore {
18400
19117
  */
18401
19118
  encryptedEntries$ = this.entries$.pipe(map((entries) => entries.filter((entry) => entry.encrypted && entry.status === 'ready')), shareReplay(1));
18402
19119
  hasReadyEntries$ = this.displayEntries$.pipe(map((entries) => entries.some((entry) => entry.status === 'ready' && !entry.ignored)), distinctUntilChanged(), shareReplay(1));
19120
+ // MARK: Page editing
19121
+ /**
19122
+ * Raw {@link DbxPdfMergeEditorConfig.pageEditing} value pushed onto the store, before defaulting. Consumed by the editor as the middle tier of its resolution chain (own `[config]` input → store → {@link DBX_PDF_MERGE_EDITOR_CONFIG} token), so a store-level default does not shadow the token.
19123
+ */
19124
+ pageEditingSetting$ = this._pageEditing$.asObservable();
19125
+ /**
19126
+ * Whether page editing is active, defaulted to {@link DEFAULT_DBX_PDF_MERGE_PAGE_EDITING}. Every page-related stream is inert while this is `false`.
19127
+ */
19128
+ pageEditing$ = this.pageEditingSetting$.pipe(map((pageEditing) => pageEditing ?? DEFAULT_DBX_PDF_MERGE_PAGE_EDITING), distinctUntilChanged(), shareReplay(1));
19129
+ /**
19130
+ * Raw {@link DbxPdfMergeEditorConfig.sidecar} value pushed onto the store, before defaulting.
19131
+ */
19132
+ sidecarSetting$ = this._sidecar$.asObservable();
19133
+ /**
19134
+ * Whether the merged output should carry an embedded manifest, defaulted to {@link DEFAULT_DBX_PDF_MERGE_SIDECAR}. Independent of {@link pageEditing$}.
19135
+ */
19136
+ sidecar$ = this.sidecarSetting$.pipe(map((sidecar) => sidecar ?? DEFAULT_DBX_PDF_MERGE_SIDECAR), distinctUntilChanged(), shareReplay(1));
19137
+ /**
19138
+ * Hydrated page metadata keyed by entry id.
19139
+ *
19140
+ * Short-circuits to an empty record while page editing is off, so the default configuration never parses a source document — the upload path's cheap header scan stays the only inspection performed. Each entry is parsed at most once thanks to {@link _pageMetaCache}, and metadata for departed entries is pruned on each pass.
19141
+ */
19142
+ pageMetas$ = combineLatest([this.entries$, this.pageEditing$]).pipe(switchMap(([entries, pageEditing]) => {
19143
+ const ready = pageEditing ? entries.filter((entry) => entry.status === 'ready') : [];
19144
+ let next$;
19145
+ if (ready.length === 0) {
19146
+ next$ = of({});
19147
+ }
19148
+ else {
19149
+ const readyIds = new Set(ready.map((entry) => entry.id));
19150
+ Array.from(this._pageMetaCache.keys()).forEach((entryId) => {
19151
+ if (!readyIds.has(entryId)) {
19152
+ this._pageMetaCache.delete(entryId);
19153
+ }
19154
+ });
19155
+ const loaded = ready.map(async (entry) => {
19156
+ let metas = this._pageMetaCache.get(entry.id);
19157
+ if (metas == null) {
19158
+ metas = readPdfMergeEntryPageMetas(entry);
19159
+ this._pageMetaCache.set(entry.id, metas);
19160
+ }
19161
+ return [entry.id, await metas];
19162
+ });
19163
+ next$ = from(Promise.all(loaded)).pipe(map((pairs) => Object.fromEntries(pairs)));
19164
+ }
19165
+ return next$;
19166
+ }), shareReplay(1));
19167
+ /**
19168
+ * The editor's ordered page plan, or `null` while page editing is disabled. Passing `null` through to the merge is what makes the default path emit every page of every entry exactly as it did before page editing existed.
19169
+ */
19170
+ pages$ = combineLatest([this.displayEntries$, this.pageEditing$, this.pageMetas$, this.select((state) => state.pageOverrides), this.select((state) => state.pageOrder)]).pipe(map(([entries, pageEditing, pageMetas, pageOverrides, pageOrder]) => {
19171
+ let pages;
19172
+ if (pageEditing) {
19173
+ pages = buildPdfMergePagePlan({ entries, pageMetas, pageOverrides, pageOrder });
19174
+ }
19175
+ else {
19176
+ pages = null;
19177
+ }
19178
+ return pages;
19179
+ }), shareReplay(1));
19180
+ /**
19181
+ * The page plan split into the groups the UI renders, one per slot plus one for unslotted entries. Empty while page editing is disabled.
19182
+ */
19183
+ pageGroups$ = this.pages$.pipe(map((pages) => {
19184
+ const grouped = makeValuesGroupMap(pages ?? [], (page) => page.groupKey);
19185
+ const groups = [];
19186
+ grouped.forEach((groupPages, groupKey) => {
19187
+ groups.push({ groupKey: groupKey, slotId: groupPages[0]?.slotId ?? null, pages: groupPages });
19188
+ });
19189
+ return groups;
19190
+ }), shareReplay(1));
19191
+ /**
19192
+ * Entries that are ready to merge but whose pages could not be listed — encrypted documents (which `pdf-lib` cannot open) and anything unparseable. Surfaced so the UI can explain why those rows are not expandable instead of letting them silently vanish from the page list. Always empty while page editing is disabled.
19193
+ */
19194
+ unexpandableEntries$ = combineLatest([this.displayEntries$, this.pageEditing$, this.pageMetas$]).pipe(map(([entries, pageEditing, pageMetas]) => (pageEditing ? entries.filter((entry) => entry.status === 'ready' && !entry.ignored && pageMetas[entry.id] === null) : [])), shareReplay(1));
19195
+ /**
19196
+ * Number of pages that will reach the merged output — the plan minus anything marked for removal. Zero while page editing is disabled, where the count is not meaningful.
19197
+ */
19198
+ mergeablePageCount$ = this.pages$.pipe(map((pages) => (pages ?? []).filter((page) => !page.removed).length), distinctUntilChanged(), shareReplay(1));
19199
+ /**
19200
+ * Whether at least one page survives the user's edits. Emits `true` while page editing is disabled so it never gates the default path.
19201
+ */
19202
+ hasMergeablePages$ = this.pages$.pipe(map((pages) => pages == null || pages.some((page) => !page.removed)), distinctUntilChanged(), shareReplay(1));
19203
+ /**
19204
+ * Returns the pages belonging to one group, for a slot rendering its own pages inline.
19205
+ *
19206
+ * @param slotId - Slot identifier, or `null` for the unslotted group.
19207
+ * @returns Observable of that group's ordered pages.
19208
+ */
19209
+ pagesForSlotId$(slotId) {
19210
+ const groupKey = pdfMergePageGroupKeyForSlotId(slotId);
19211
+ return this.pages$.pipe(map((pages) => (pages ?? []).filter((page) => page.groupKey === groupKey)), shareReplay(1));
19212
+ }
18403
19213
  /**
18404
19214
  * Emits `true` while any entry's validation promise has not yet resolved (i.e. one or more entries are still in `validating` status). Reads from {@link entries$} (not {@link displayEntries$}) so validation gating ignores the `ignored`/`error` projection done by encryption handling.
18405
19215
  */
@@ -18411,15 +19221,17 @@ class DbxPdfMergeEditorStore extends ComponentStore {
18411
19221
  /**
18412
19222
  * Internal pre-validity merge stream produced without consulting {@link isValid$}. Drives both {@link outputSize$} and the eventual {@link currentMergeOutput$} so size-based gating can observe the would-be blob without creating a cycle. Consumes {@link displayEntries$} so the merge respects the active {@link DbxPdfMergeEncryptedHandling} (encrypted-focused entries pass through, ignored entries are dropped, `error` mode demotions are honored).
18413
19223
  */
18414
- _candidateMergeOutput$ = combineLatest([this.displayEntries$, this.isValidating$, this.validatorValid$]).pipe(switchMap(([entries, isValidating, validatorValid]) => {
19224
+ _candidateMergeOutput$ = combineLatest([this.displayEntries$, this.isValidating$, this.validatorValid$, this.pages$, this.sidecar$]).pipe(switchMap(([entries, isValidating, validatorValid, pages, sidecar]) => {
18415
19225
  const mergeable = entries.filter((entry) => !entry.ignored);
18416
19226
  const hasReady = mergeable.some((entry) => entry.status === 'ready');
19227
+ // A `null` plan means page editing is off, in which case the merge takes its original every-page path.
19228
+ const hasPages = pages == null || pages.some((page) => !page.removed);
18417
19229
  let next$;
18418
- if (isValidating || !hasReady || !validatorValid) {
19230
+ if (isValidating || !hasReady || !validatorValid || !hasPages) {
18419
19231
  next$ = of(undefined);
18420
19232
  }
18421
19233
  else {
18422
- next$ = from(mergePdfMergeEntries(mergeable)).pipe(catchError(() => of(undefined)));
19234
+ next$ = from(mergePdfMergeEntries(mergeable, { pages, sidecar })).pipe(catchError(() => of(undefined)));
18423
19235
  }
18424
19236
  return next$;
18425
19237
  }), shareReplay(1));
@@ -18441,9 +19253,11 @@ class DbxPdfMergeEditorStore extends ComponentStore {
18441
19253
  return valid;
18442
19254
  }), distinctUntilChanged(), shareReplay(1));
18443
19255
  /**
18444
- * Emits `true` while both the registered {@link DbxPdfMergeEditorValidator} delegate (if any) and the optional output-size limit are satisfied. {@link currentMergeOutput$} gates merge emissions on this stream.
19256
+ * Emits `true` while the registered {@link DbxPdfMergeEditorValidator} delegate (if any), the optional output-size limit, and the page plan are all satisfied. {@link currentMergeOutput$} gates merge emissions on this stream.
19257
+ *
19258
+ * The page-plan term is `true` whenever page editing is disabled, so this stream is unchanged on the default path. With page editing on it blocks the merge once the user has marked every page for deletion.
18445
19259
  */
18446
- isValid$ = combineLatest([this.validatorValid$, this.sizeLimitValid$]).pipe(map(([validatorValid, sizeLimitValid]) => validatorValid && sizeLimitValid), distinctUntilChanged(), shareReplay(1));
19260
+ isValid$ = combineLatest([this.validatorValid$, this.sizeLimitValid$, this.hasMergeablePages$]).pipe(map(([validatorValid, sizeLimitValid, hasMergeablePages]) => validatorValid && sizeLimitValid && hasMergeablePages), distinctUntilChanged(), shareReplay(1));
18447
19261
  /**
18448
19262
  * Emits the merged PDF blob whenever every entry has finished validating (see {@link isValidating$}), at least one is `ready`, and {@link isValid$} reports `true`. Emits `undefined` while validation is in flight, when the list is empty, when the delegate or size limit reports invalid, or when the most recent merge failed.
18449
19263
  */
@@ -18462,6 +19276,120 @@ class DbxPdfMergeEditorStore extends ComponentStore {
18462
19276
  entriesForSlotId$(slotId) {
18463
19277
  return this.displayEntries$.pipe(map((entries) => entries.filter((entry) => entry.slotId === slotId)), shareReplay(1));
18464
19278
  }
19279
+ // MARK: Slot registry
19280
+ /**
19281
+ * Slot ids currently mounted against this store, i.e. the sections this editor declares.
19282
+ *
19283
+ * This is what makes `<dbx-pdf-merge-import>` a zero-configuration drop-in: it derives the set of sections a re-imported file is allowed to name (and whether it renders at all) from the slots actually on screen, rather than from a hand-maintained list duplicating them.
19284
+ *
19285
+ * Emitted **sorted ascending**, which is deterministic but is NOT declaration order. Sorting is what lets the guard below collapse slot churn: a set cycling through `@if` (`['a','b']` → `['b']` → `['b','a']`) ends up equal to the array it started with, where registration order would emit a spuriously different array for an identical set.
19286
+ *
19287
+ * Registered by {@link DbxPdfMergeEditorFileUploadComponent} from its own lifecycle. Kept here rather than on the optional {@link DbxPdfMergeEditorFileUploadValidatorDirective} because it must be available whether or not a validator is present — and because a separately-provided registry could be provided at a different level than the store, letting slots register with one instance while a reader sees another.
19288
+ */
19289
+ registeredSlotIds$ = this._registeredSlotIds$.pipe(distinctUntilChanged((a, b) => a.length === b.length && a.every((slotId, i) => slotId === b[i])), shareReplay(1));
19290
+ /**
19291
+ * Registers a slot id as mounted, adding it to {@link registeredSlotIds$}.
19292
+ *
19293
+ * Reference-counted rather than a set: the same `slotId` can legitimately be mounted twice at once — the PDF merge upload dialog hosts its slots against an ancestor store while inline slots may still exist under {@link DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY} — and one unmount must not deregister a still-mounted id.
19294
+ *
19295
+ * @param slotId - Slot identifier being mounted.
19296
+ */
19297
+ registerSlotId(slotId) {
19298
+ this._slotIdCounts.set(slotId, (this._slotIdCounts.get(slotId) ?? 0) + 1);
19299
+ this._emitRegisteredSlotIds();
19300
+ }
19301
+ /**
19302
+ * Deregisters one mount of a slot id, removing it from {@link registeredSlotIds$} once the last mount is gone. Unknown ids are ignored.
19303
+ *
19304
+ * Callers must pair this with their own {@link registerSlotId} exactly once. The store frequently outlives its slots (a dialog-hosted slot is destroyed on every close while the page-level store lives on), so a missed call leaves the editor permanently claiming a section that is not on screen.
19305
+ *
19306
+ * @param slotId - Slot identifier being unmounted.
19307
+ */
19308
+ unregisterSlotId(slotId) {
19309
+ const count = this._slotIdCounts.get(slotId);
19310
+ if (count != null) {
19311
+ if (count > 1) {
19312
+ this._slotIdCounts.set(slotId, count - 1);
19313
+ }
19314
+ else {
19315
+ this._slotIdCounts.delete(slotId);
19316
+ }
19317
+ this._emitRegisteredSlotIds();
19318
+ }
19319
+ }
19320
+ _emitRegisteredSlotIds() {
19321
+ const next = Array.from(this._slotIdCounts.keys()).sort();
19322
+ const current = this._registeredSlotIds$.value;
19323
+ if (next.length !== current.length || next.some((slotId, i) => slotId !== current[i])) {
19324
+ this._registeredSlotIds$.next(next);
19325
+ }
19326
+ }
19327
+ // MARK: Import
19328
+ /**
19329
+ * Lifecycle of the most recent {@link importMergedPdf} call, or `undefined` before the first. Lets a consumer that triggered a programmatic import observe its outcome — the picker component is not the only entry point.
19330
+ */
19331
+ importState$ = this._importState$.asObservable();
19332
+ /**
19333
+ * The most recent successful import, or `null` while none has succeeded.
19334
+ */
19335
+ importResult$ = this.importState$.pipe(map((state) => (state?.status === 'imported' ? state.result : null)), distinctUntilChanged(), shareReplay(1));
19336
+ /**
19337
+ * Why the most recent import failed, or `null` when it did not.
19338
+ */
19339
+ importError$ = this.importState$.pipe(map((state) => (state?.status === 'failed' ? state.error : null)), distinctUntilChanged(), shareReplay(1));
19340
+ /**
19341
+ * Emits `true` while an import is in flight.
19342
+ */
19343
+ isImporting$ = this.importState$.pipe(map((state) => state?.status === 'importing'), distinctUntilChanged(), shareReplay(1));
19344
+ /**
19345
+ * Imports a previously-exported merged PDF, replacing every entry currently in the store with the per-slot documents recorded in its manifest.
19346
+ *
19347
+ * This is the programmatic equivalent of what {@link DbxPdfMergeImportComponent} does with a picked file, and that component delegates here — so a stored document loaded by an app and a file chosen by a user go through one implementation with one set of error semantics. Progress and outcome are published on {@link importState$} as well as returned.
19348
+ *
19349
+ * @param input - The source blob plus the optional slot check and sidecar-less fallback.
19350
+ * @returns The terminal state of this import.
19351
+ */
19352
+ async importMergedPdf(input) {
19353
+ const { source, expectedSlotIds, allowWithoutSidecar, fileName } = input;
19354
+ const config = { allowWithoutSidecar, fileName };
19355
+ this._importState$.next({ status: 'importing' });
19356
+ const outcome = await buildPdfMergeEntriesFromSidecar(source, config);
19357
+ let state;
19358
+ if ('error' in outcome) {
19359
+ state = { status: 'failed', error: outcome.error };
19360
+ }
19361
+ else {
19362
+ // An unsectioned document counts as unexpected too: in a slots-only editor it would render
19363
+ // nowhere, so rejecting it beats importing pages the user can never see again.
19364
+ const unexpected = expectedSlotIds == null ? [] : outcome.slotIds.filter((slotId) => slotId == null || !expectedSlotIds.includes(slotId));
19365
+ if (unexpected.length > 0) {
19366
+ state = { status: 'failed', error: 'unexpected_slots', unexpectedSlotIds: unexpected };
19367
+ }
19368
+ else {
19369
+ const missingSlotIds = expectedSlotIds == null ? [] : expectedSlotIds.filter((slotId) => !outcome.slotIds.includes(slotId));
19370
+ this.replaceEntries(outcome.entries);
19371
+ state = { status: 'imported', result: { ...outcome, missingSlotIds } };
19372
+ }
19373
+ }
19374
+ this._importState$.next(state);
19375
+ return state;
19376
+ }
19377
+ /**
19378
+ * Adds a single document to a slot, without any sidecar involvement — the programmatic equivalent of a user dropping one file onto a slot uploader. Existing entries are left alone.
19379
+ *
19380
+ * Unlike {@link importMergedPdf} this neither reads nor requires a manifest; it is the plain "put this file in that slot" primitive, for e.g. an admin appending one document to an existing set. The entry can be added before its slot mounts — {@link entriesForSlotId$} picks it up when the slot renders.
19381
+ *
19382
+ * @param input - The blob or file, its target slot, and an optional name for a bare blob.
19383
+ * @returns The entry that was added, or `null` when the file is not a supported PDF/PNG/JPEG (in which case nothing was added).
19384
+ */
19385
+ async addFileToSlot(input) {
19386
+ const { file, slotId, fileName, imageCompression } = input;
19387
+ const entry = await buildPdfMergeEntry(asPdfMergeFile(file, fileName), { slotId, imageCompression: imageCompression ?? this._imageCompression$.value });
19388
+ if (entry != null) {
19389
+ this.addFiles({ entries: [entry] });
19390
+ }
19391
+ return entry;
19392
+ }
18465
19393
  // MARK: Validator
18466
19394
  /**
18467
19395
  * Registers a {@link DbxPdfMergeEditorValidator} delegate that gates merge emissions. Only one delegate is active at a time — calling this replaces any previously registered delegate.
@@ -18501,6 +19429,22 @@ class DbxPdfMergeEditorStore extends ComponentStore {
18501
19429
  setEncryptedHandling(handling) {
18502
19430
  this._encryptedHandling$.next(handling ?? undefined);
18503
19431
  }
19432
+ /**
19433
+ * Enables or disables page editing, exposed via {@link pageEditing$}. Turning it on expands each ready entry into its individual pages; turning it off returns the editor to its file-granular behavior while preserving any page edits in state, so toggling back on restores them.
19434
+ *
19435
+ * @param pageEditing - Whether page editing is active, or a falsy value to clear and fall back to {@link DEFAULT_DBX_PDF_MERGE_PAGE_EDITING}.
19436
+ */
19437
+ setPageEditing(pageEditing) {
19438
+ this._pageEditing$.next(pageEditing ?? undefined);
19439
+ }
19440
+ /**
19441
+ * Enables or disables the embedded manifest, exposed via {@link sidecar$}.
19442
+ *
19443
+ * @param sidecar - Whether to embed the manifest, or a falsy value to clear and fall back to {@link DEFAULT_DBX_PDF_MERGE_SIDECAR}.
19444
+ */
19445
+ setSidecar(sidecar) {
19446
+ this._sidecar$.next(sidecar ?? undefined);
19447
+ }
18504
19448
  // MARK: Updaters
18505
19449
  /**
18506
19450
  * Appends entries (already constructed) or builds them from raw files and appends them to state. Each entry's validation promise starts when the entry is built; {@link entries$} reflects each result as it resolves. When `input` is an object with `files` and `slotId`, the resulting entries are tagged with that slot id. When `input` is `{ entries }`, the entries are appended as-is — use this shape for entries that went through async client-side compression upstream.
@@ -18523,11 +19467,17 @@ class DbxPdfMergeEditorStore extends ComponentStore {
18523
19467
  }
18524
19468
  return newEntries.length > 0 ? { ...state, rawEntries: [...state.rawEntries, ...newEntries] } : state;
18525
19469
  });
18526
- removeEntry = this.updater((state, id) => ({ ...state, rawEntries: state.rawEntries.filter((entry) => entry.id !== id) }));
19470
+ removeEntry = this.updater((state, id) => {
19471
+ const rawEntries = state.rawEntries.filter((entry) => entry.id !== id);
19472
+ return { ...state, rawEntries, ...prunePdfMergePageState(state, new Set(rawEntries.map((entry) => entry.id))) };
19473
+ });
18527
19474
  /**
18528
19475
  * Removes every entry whose `slotId` matches the given id. Used by {@link DbxPdfMergeEditorFileUploadComponent} on destroy so a slot's entries leave with it.
18529
19476
  */
18530
- removeEntriesBySlotId = this.updater((state, slotId) => ({ ...state, rawEntries: state.rawEntries.filter((entry) => entry.slotId !== slotId) }));
19477
+ removeEntriesBySlotId = this.updater((state, slotId) => {
19478
+ const rawEntries = state.rawEntries.filter((entry) => entry.slotId !== slotId);
19479
+ return { ...state, rawEntries, ...prunePdfMergePageState(state, new Set(rawEntries.map((entry) => entry.id))) };
19480
+ });
18531
19481
  moveEntry = this.updater((state, move) => {
18532
19482
  let nextState;
18533
19483
  if (move.previousIndex === move.currentIndex) {
@@ -18567,7 +19517,49 @@ class DbxPdfMergeEditorStore extends ComponentStore {
18567
19517
  }
18568
19518
  return nextState;
18569
19519
  });
18570
- clearAll = this.updater((state) => ({ ...state, rawEntries: [] }));
19520
+ clearAll = this.updater((state) => ({ ...state, rawEntries: [], pageOverrides: {}, pageOrder: {} }));
19521
+ /**
19522
+ * Replaces the entire entry list, discarding any page edits.
19523
+ *
19524
+ * Used by the sidecar import path, which reconstructs a fresh set of slot-tagged entries from a previously-exported file. Done as a single state change so subscribers never observe an intermediate empty list.
19525
+ */
19526
+ replaceEntries = this.updater((state, entries) => ({ ...state, rawEntries: [...entries], pageOverrides: {}, pageOrder: {} }));
19527
+ // MARK: Page updaters
19528
+ /**
19529
+ * Reorders a page inside its own group. Groups never exchange pages — the page list renders one CDK drop list per group, so a cross-group drag is impossible to express.
19530
+ *
19531
+ * The move rewrites the id array the caller rendered rather than recomputing it from state, keeping the stored order aligned with what the user saw.
19532
+ */
19533
+ movePageWithinGroup = this.updater((state, move) => {
19534
+ let nextState;
19535
+ if (move.previousIndex === move.currentIndex || move.previousIndex < 0 || move.currentIndex < 0 || move.previousIndex >= move.pageIds.length || move.currentIndex >= move.pageIds.length) {
19536
+ nextState = state;
19537
+ }
19538
+ else {
19539
+ const pageIds = [...move.pageIds];
19540
+ moveItemInArray(pageIds, move.previousIndex, move.currentIndex);
19541
+ nextState = { ...state, pageOrder: { ...state.pageOrder, [move.groupKey]: pageIds } };
19542
+ }
19543
+ return nextState;
19544
+ });
19545
+ /**
19546
+ * Sets a page's rotation. The value is composed onto the page's own source rotation at merge time, so this is the rotation the user applied rather than the page's absolute orientation.
19547
+ */
19548
+ setPageRotation = this.updater((state, change) => {
19549
+ const current = state.pageOverrides[change.pageId];
19550
+ return { ...state, pageOverrides: { ...state.pageOverrides, [change.pageId]: { rotation: change.rotation, removed: current?.removed ?? false } } };
19551
+ });
19552
+ /**
19553
+ * Marks a page for deletion or restores it. Marked pages stay in the list so the choice can be undone, but are excluded from the merged output.
19554
+ */
19555
+ setPageRemoved = this.updater((state, change) => {
19556
+ const current = state.pageOverrides[change.pageId];
19557
+ return { ...state, pageOverrides: { ...state.pageOverrides, [change.pageId]: { rotation: current?.rotation ?? 0, removed: change.removed } } };
19558
+ });
19559
+ /**
19560
+ * Discards every page edit, returning the plan to natural order with no rotations or deletions. Leaves the entries themselves untouched.
19561
+ */
19562
+ clearPageEdits = this.updater((state) => ({ ...state, pageOverrides: {}, pageOrder: {} }));
18571
19563
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
18572
19564
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorStore });
18573
19565
  }
@@ -18592,6 +19584,19 @@ class DbxPdfMergeEditorStoreDirective {
18592
19584
  _injectedConfig = inject(DBX_PDF_MERGE_EDITOR_CONFIG, { optional: true });
18593
19585
  config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
18594
19586
  outputSizeLimit = input(...(ngDevMode ? [undefined, { debugName: "outputSizeLimit" }] : /* istanbul ignore next */ []));
19587
+ /**
19588
+ * A previously-exported merged PDF to load into the store, for an app that fetches a stored document itself and wants the editor to open already populated — no picker involved.
19589
+ *
19590
+ * Imported once per distinct blob instance: re-emitting the same object does nothing, so a template re-render or an unrelated input change cannot re-import. Emitting a *different* blob imports again and replaces the entries, even if the bytes are identical.
19591
+ *
19592
+ * Watch {@link DbxPdfMergeEditorStore.importState$} for the outcome — a failure surfaces there rather than throwing.
19593
+ */
19594
+ source = input(...(ngDevMode ? [undefined, { debugName: "source" }] : /* istanbul ignore next */ []));
19595
+ sourceConfig = input(...(ngDevMode ? [undefined, { debugName: "sourceConfig" }] : /* istanbul ignore next */ []));
19596
+ /**
19597
+ * The blob instance most recently handed to the store, so the same one is never imported twice.
19598
+ */
19599
+ _importedSource;
18595
19600
  effectiveErrorBytesSignal = computed(() => {
18596
19601
  const direct = this.outputSizeLimit();
18597
19602
  if (direct != null) {
@@ -18623,9 +19628,30 @@ class DbxPdfMergeEditorStoreDirective {
18623
19628
  effect(() => {
18624
19629
  this.store.setEncryptedHandling(this.config()?.encryptedHandling);
18625
19630
  });
19631
+ // Same channel for pageEditing, so a dialog-hosted editor opens straight into page
19632
+ // editing mode when the host directive asked for it.
19633
+ effect(() => {
19634
+ this.store.setPageEditing(this.config()?.pageEditing);
19635
+ });
19636
+ // Same channel for the embedded manifest. Gated separately from pageEditing — a plain
19637
+ // slot merge benefits from recording which pages came from which slot too.
19638
+ effect(() => {
19639
+ this.store.setSidecar(this.config()?.sidecar);
19640
+ });
19641
+ // Loading a source is not idempotent the way the setters above are — it REPLACES the store's
19642
+ // entries — so this tracks only `source` and guards on instance identity. `sourceConfig` is
19643
+ // read untracked so changing it never re-imports; it is read at the moment a new blob arrives.
19644
+ effect(() => {
19645
+ const source = this.source();
19646
+ if (source != null && source !== this._importedSource) {
19647
+ this._importedSource = source;
19648
+ const sourceConfig = untracked(() => this.sourceConfig());
19649
+ void this.store.importMergedPdf({ source, expectedSlotIds: sourceConfig?.expectedSlotIds, allowWithoutSidecar: sourceConfig?.allowWithoutSidecar, fileName: sourceConfig?.fileName });
19650
+ }
19651
+ });
18626
19652
  }
18627
19653
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorStoreDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
18628
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.11", type: DbxPdfMergeEditorStoreDirective, isStandalone: true, selector: "[dbxPdfMergeEditorStore]", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, outputSizeLimit: { classPropertyName: "outputSizeLimit", publicName: "outputSizeLimit", isSignal: true, isRequired: false, transformFunction: null } }, providers: [DbxPdfMergeEditorStore], exportAs: ["dbxPdfMergeEditorStore"], ngImport: i0 });
19654
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.11", type: DbxPdfMergeEditorStoreDirective, isStandalone: true, selector: "[dbxPdfMergeEditorStore]", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, outputSizeLimit: { classPropertyName: "outputSizeLimit", publicName: "outputSizeLimit", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, sourceConfig: { classPropertyName: "sourceConfig", publicName: "sourceConfig", isSignal: true, isRequired: false, transformFunction: null } }, providers: [DbxPdfMergeEditorStore], exportAs: ["dbxPdfMergeEditorStore"], ngImport: i0 });
18629
19655
  }
18630
19656
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorStoreDirective, decorators: [{
18631
19657
  type: Directive,
@@ -18635,10 +19661,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
18635
19661
  providers: [DbxPdfMergeEditorStore],
18636
19662
  exportAs: 'dbxPdfMergeEditorStore'
18637
19663
  }]
18638
- }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], outputSizeLimit: [{ type: i0.Input, args: [{ isSignal: true, alias: "outputSizeLimit", required: false }] }] } });
19664
+ }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], outputSizeLimit: [{ type: i0.Input, args: [{ isSignal: true, alias: "outputSizeLimit", required: false }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], sourceConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceConfig", required: false }] }] } });
18639
19665
 
18640
- const PDF_ICON = 'picture_as_pdf';
18641
- const IMAGE_ICON = 'image';
19666
+ const PDF_ICON$1 = 'picture_as_pdf';
19667
+ const IMAGE_ICON$1 = 'image';
18642
19668
  const ERROR_ICON = 'error';
18643
19669
  /**
18644
19670
  * Single row inside the {@link DbxPdfMergeListComponent}: shows the file's icon, name, formatted size, status, drag handle, and a remove button. The component's template is itself a `cdkDrag` element so each row can be reordered inside the parent's `cdkDropList`.
@@ -18655,10 +19681,10 @@ class DbxPdfMergeEntryComponent {
18655
19681
  icon = ERROR_ICON;
18656
19682
  }
18657
19683
  else if (entry.kind === 'pdf') {
18658
- icon = PDF_ICON;
19684
+ icon = PDF_ICON$1;
18659
19685
  }
18660
19686
  else {
18661
- icon = IMAGE_ICON;
19687
+ icon = IMAGE_ICON$1;
18662
19688
  }
18663
19689
  return icon;
18664
19690
  }, ...(ngDevMode ? [{ debugName: "iconSignal" }] : /* istanbul ignore next */ []));
@@ -18844,6 +19870,275 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
18844
19870
  }]
18845
19871
  }] });
18846
19872
 
19873
+ const PDF_ICON = 'picture_as_pdf';
19874
+ const IMAGE_ICON = 'image';
19875
+ /**
19876
+ * Degrees added by one press of the rotate control.
19877
+ */
19878
+ const ROTATION_STEP_DEGREES = 90;
19879
+ /**
19880
+ * PDF points per inch. PDF page dimensions are expressed in points; inches read better on a scanned document.
19881
+ */
19882
+ const POINTS_PER_INCH = 72;
19883
+ const ROTATION_VALUES = [0, 90, 180, 270];
19884
+ /**
19885
+ * Single page row inside a {@link DbxPdfMergePageListComponent} group: shows which source file and page it came from, its dimensions and rotation, and controls to rotate it, mark it for removal, and drag it to a new position.
19886
+ *
19887
+ * The template's root element is itself a `cdkDrag`, matching {@link DbxPdfMergeEntryComponent}, so rows reorder inside their group's drop list.
19888
+ */
19889
+ class DbxPdfMergePageComponent {
19890
+ store = inject(DbxPdfMergeEditorStore);
19891
+ page = input.required(...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
19892
+ removedSignal = computed(() => this.page().removed, ...(ngDevMode ? [{ debugName: "removedSignal" }] : /* istanbul ignore next */ []));
19893
+ iconSignal = computed(() => (this.page().kind === 'pdf' ? PDF_ICON : IMAGE_ICON), ...(ngDevMode ? [{ debugName: "iconSignal" }] : /* istanbul ignore next */ []));
19894
+ removeLabelSignal = computed(() => (this.removedSignal() ? 'Restore page' : 'Mark page for removal'), ...(ngDevMode ? [{ debugName: "removeLabelSignal" }] : /* istanbul ignore next */ []));
19895
+ positionLabelSignal = computed(() => {
19896
+ const page = this.page();
19897
+ let label;
19898
+ if (page.kind === 'image') {
19899
+ label = 'Image';
19900
+ }
19901
+ else {
19902
+ label = `Page ${page.sourceIndex + 1} of ${page.sourcePageCount}`;
19903
+ }
19904
+ return label;
19905
+ }, ...(ngDevMode ? [{ debugName: "positionLabelSignal" }] : /* istanbul ignore next */ []));
19906
+ dimensionsLabelSignal = computed(() => {
19907
+ const { kind, meta } = this.page();
19908
+ let label;
19909
+ if (meta.width <= 0 || meta.height <= 0) {
19910
+ label = null;
19911
+ }
19912
+ else if (kind === 'image') {
19913
+ label = `${Math.round(meta.width)} × ${Math.round(meta.height)} px`;
19914
+ }
19915
+ else {
19916
+ label = `${(meta.width / POINTS_PER_INCH).toFixed(1)} × ${(meta.height / POINTS_PER_INCH).toFixed(1)} in`;
19917
+ }
19918
+ return label;
19919
+ }, ...(ngDevMode ? [{ debugName: "dimensionsLabelSignal" }] : /* istanbul ignore next */ []));
19920
+ rotationLabelSignal = computed(() => {
19921
+ const rotation = this.page().rotation;
19922
+ return rotation === 0 ? null : `${rotation}°`;
19923
+ }, ...(ngDevMode ? [{ debugName: "rotationLabelSignal" }] : /* istanbul ignore next */ []));
19924
+ onRotate() {
19925
+ const page = this.page();
19926
+ const next = ROTATION_VALUES[((page.rotation + ROTATION_STEP_DEGREES) / ROTATION_STEP_DEGREES) % ROTATION_VALUES.length];
19927
+ this.store.setPageRotation({ pageId: page.id, rotation: next });
19928
+ }
19929
+ onToggleRemoved() {
19930
+ const page = this.page();
19931
+ this.store.setPageRemoved({ pageId: page.id, removed: !page.removed });
19932
+ }
19933
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergePageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19934
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergePageComponent, isStandalone: true, selector: "dbx-pdf-merge-page", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.dbx-pdf-merge-page--removed": "removedSignal()" }, classAttribute: "dbx-pdf-merge-page d-block" }, ngImport: i0, template: `
19935
+ <div class="dbx-pdf-merge-page-row" cdkDrag cdkDragLockAxis="y">
19936
+ <ng-template cdkDragPlaceholder>
19937
+ <div class="dbx-pdf-merge-page-placeholder"></div>
19938
+ </ng-template>
19939
+ <button mat-icon-button type="button" cdkDragHandle class="dbx-pdf-merge-page-handle" aria-label="Drag to reorder page">
19940
+ <mat-icon>drag_indicator</mat-icon>
19941
+ </button>
19942
+ <mat-icon class="dbx-pdf-merge-page-icon">{{ iconSignal() }}</mat-icon>
19943
+ <div class="dbx-pdf-merge-page-info dbx-flex-fill-0">
19944
+ <div class="dbx-pdf-merge-page-name dbx-text-truncate" [title]="page().sourceName">{{ page().sourceName }}</div>
19945
+ <div class="dbx-pdf-merge-page-meta dbx-hint dbx-small">
19946
+ <span class="dbx-pdf-merge-page-position">{{ positionLabelSignal() }}</span>
19947
+ @if (dimensionsLabelSignal(); as dimensions) {
19948
+ <span class="dbx-pdf-merge-page-dimensions">{{ dimensions }}</span>
19949
+ }
19950
+ @if (rotationLabelSignal(); as rotation) {
19951
+ <dbx-chip class="dbx-pdf-merge-page-rotation-chip" [small]="true">
19952
+ <mat-icon class="dbx-pdf-merge-page-rotation-icon">rotate_right</mat-icon>
19953
+ <span>{{ rotation }}</span>
19954
+ </dbx-chip>
19955
+ }
19956
+ @if (removedSignal()) {
19957
+ <span class="dbx-pdf-merge-page-removed-label dbx-warn">Will be removed</span>
19958
+ }
19959
+ </div>
19960
+ </div>
19961
+ <button mat-icon-button type="button" class="dbx-pdf-merge-page-rotate" [disabled]="removedSignal()" (click)="onRotate()" aria-label="Rotate page 90 degrees">
19962
+ <mat-icon>rotate_right</mat-icon>
19963
+ </button>
19964
+ <button mat-icon-button type="button" class="dbx-pdf-merge-page-remove" (click)="onToggleRemoved()" [attr.aria-label]="removeLabelSignal()">
19965
+ <mat-icon>{{ removedSignal() ? 'undo' : 'close' }}</mat-icon>
19966
+ </button>
19967
+ </div>
19968
+ `, isInline: true, dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: DbxChipDirective, selector: "dbx-chip", inputs: ["small", "block", "color", "display", "tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19969
+ }
19970
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergePageComponent, decorators: [{
19971
+ type: Component,
19972
+ args: [{
19973
+ selector: 'dbx-pdf-merge-page',
19974
+ template: `
19975
+ <div class="dbx-pdf-merge-page-row" cdkDrag cdkDragLockAxis="y">
19976
+ <ng-template cdkDragPlaceholder>
19977
+ <div class="dbx-pdf-merge-page-placeholder"></div>
19978
+ </ng-template>
19979
+ <button mat-icon-button type="button" cdkDragHandle class="dbx-pdf-merge-page-handle" aria-label="Drag to reorder page">
19980
+ <mat-icon>drag_indicator</mat-icon>
19981
+ </button>
19982
+ <mat-icon class="dbx-pdf-merge-page-icon">{{ iconSignal() }}</mat-icon>
19983
+ <div class="dbx-pdf-merge-page-info dbx-flex-fill-0">
19984
+ <div class="dbx-pdf-merge-page-name dbx-text-truncate" [title]="page().sourceName">{{ page().sourceName }}</div>
19985
+ <div class="dbx-pdf-merge-page-meta dbx-hint dbx-small">
19986
+ <span class="dbx-pdf-merge-page-position">{{ positionLabelSignal() }}</span>
19987
+ @if (dimensionsLabelSignal(); as dimensions) {
19988
+ <span class="dbx-pdf-merge-page-dimensions">{{ dimensions }}</span>
19989
+ }
19990
+ @if (rotationLabelSignal(); as rotation) {
19991
+ <dbx-chip class="dbx-pdf-merge-page-rotation-chip" [small]="true">
19992
+ <mat-icon class="dbx-pdf-merge-page-rotation-icon">rotate_right</mat-icon>
19993
+ <span>{{ rotation }}</span>
19994
+ </dbx-chip>
19995
+ }
19996
+ @if (removedSignal()) {
19997
+ <span class="dbx-pdf-merge-page-removed-label dbx-warn">Will be removed</span>
19998
+ }
19999
+ </div>
20000
+ </div>
20001
+ <button mat-icon-button type="button" class="dbx-pdf-merge-page-rotate" [disabled]="removedSignal()" (click)="onRotate()" aria-label="Rotate page 90 degrees">
20002
+ <mat-icon>rotate_right</mat-icon>
20003
+ </button>
20004
+ <button mat-icon-button type="button" class="dbx-pdf-merge-page-remove" (click)="onToggleRemoved()" [attr.aria-label]="removeLabelSignal()">
20005
+ <mat-icon>{{ removedSignal() ? 'undo' : 'close' }}</mat-icon>
20006
+ </button>
20007
+ </div>
20008
+ `,
20009
+ host: {
20010
+ class: 'dbx-pdf-merge-page d-block',
20011
+ '[class.dbx-pdf-merge-page--removed]': 'removedSignal()'
20012
+ },
20013
+ imports: [CdkDrag, CdkDragHandle, CdkDragPlaceholder, MatIconModule, MatButtonModule, DbxChipDirective],
20014
+ changeDetection: ChangeDetectionStrategy.OnPush,
20015
+ standalone: true
20016
+ }]
20017
+ }], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }] } });
20018
+
20019
+ /**
20020
+ * Message shown for an entry that is not encrypted but still could not be parsed into pages.
20021
+ */
20022
+ const UNREADABLE_PAGES_MESSAGE = 'Pages could not be read.';
20023
+ /**
20024
+ * Renders the editor's page plan while page editing is enabled, one CDK drop list per group.
20025
+ *
20026
+ * Separate, unconnected drop lists are what confine reordering to a single slot: CDK will not let a drag cross into a list it is not connected to, so the rule needs no guard code. Bind {@link slotId} to render only that slot's group — used by {@link DbxPdfMergeEditorFileUploadComponent} to show its own pages inline. Leave it unbound to render every group.
20027
+ *
20028
+ * Entries that could not be expanded into pages (encrypted, or unparseable) are listed separately below the groups rather than silently omitted.
20029
+ */
20030
+ class DbxPdfMergePageListComponent {
20031
+ store = inject(DbxPdfMergeEditorStore);
20032
+ /**
20033
+ * Renders only this slot's group when bound. Leave unbound to render every group.
20034
+ */
20035
+ slotId = input(...(ngDevMode ? [undefined, { debugName: "slotId" }] : /* istanbul ignore next */ []));
20036
+ /**
20037
+ * Whether to show each group's slot name above its pages. Defaults to `true` when rendering every group and `false` when scoped to one slot, where the slot already has its own heading.
20038
+ */
20039
+ showGroupLabels = input(...(ngDevMode ? [undefined, { debugName: "showGroupLabels" }] : /* istanbul ignore next */ []));
20040
+ _slotId$ = toObservable(this.slotId);
20041
+ groups$ = this._slotId$.pipe(switchMap((slotId) => {
20042
+ let groups$;
20043
+ if (slotId == null) {
20044
+ groups$ = this.store.pageGroups$;
20045
+ }
20046
+ else {
20047
+ const groupKey = pdfMergePageGroupKeyForSlotId(slotId);
20048
+ groups$ = this.store.pagesForSlotId$(slotId).pipe(map((pages) => (pages.length === 0 ? [] : [{ groupKey, slotId, pages }])));
20049
+ }
20050
+ return groups$;
20051
+ }), shareReplay(1));
20052
+ unexpandable$ = combineLatest([this.store.unexpandableEntries$, this._slotId$]).pipe(map(([entries, slotId]) => (slotId == null ? entries : entries.filter((entry) => entry.slotId === slotId))), shareReplay(1));
20053
+ groupsSignal = toSignal(this.groups$, { initialValue: [] });
20054
+ unexpandableSignal = toSignal(this.unexpandable$, { initialValue: [] });
20055
+ showGroupLabelsSignal = computed(() => {
20056
+ const slotId = this.slotId();
20057
+ return this.showGroupLabels() ?? slotId == null;
20058
+ }, ...(ngDevMode ? [{ debugName: "showGroupLabelsSignal" }] : /* istanbul ignore next */ []));
20059
+ labelForGroup(group) {
20060
+ return this.showGroupLabelsSignal() ? group.slotId : null;
20061
+ }
20062
+ messageForUnexpandable(entry) {
20063
+ return entry.encrypted ? DBX_PDF_MERGE_ENCRYPTED_NOT_EDITABLE_MESSAGE : UNREADABLE_PAGES_MESSAGE;
20064
+ }
20065
+ onDrop(group, event) {
20066
+ this.store.movePageWithinGroup({
20067
+ groupKey: group.groupKey,
20068
+ pageIds: group.pages.map((page) => page.id),
20069
+ previousIndex: event.previousIndex,
20070
+ currentIndex: event.currentIndex
20071
+ });
20072
+ }
20073
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergePageListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20074
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergePageListComponent, isStandalone: true, selector: "dbx-pdf-merge-page-list", inputs: { slotId: { classPropertyName: "slotId", publicName: "slotId", isSignal: true, isRequired: false, transformFunction: null }, showGroupLabels: { classPropertyName: "showGroupLabels", publicName: "showGroupLabels", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "dbx-pdf-merge-page-list d-block" }, ngImport: i0, template: `
20075
+ @if (groupsSignal(); as groups) {
20076
+ @if (groups.length === 0 && unexpandableSignal().length === 0) {
20077
+ <div class="dbx-list-empty-content dbx-hint dbx-p4">No pages yet.</div>
20078
+ } @else {
20079
+ @for (group of groups; track group.groupKey) {
20080
+ <div class="dbx-pdf-merge-page-group">
20081
+ @if (labelForGroup(group); as label) {
20082
+ <div class="dbx-pdf-merge-page-group-label dbx-hint dbx-small">{{ label }}</div>
20083
+ }
20084
+ <div class="dbx-pdf-merge-page-group-items" cdkDropList (cdkDropListDropped)="onDrop(group, $event)">
20085
+ @for (page of group.pages; track page.id) {
20086
+ <dbx-pdf-merge-page [page]="page"></dbx-pdf-merge-page>
20087
+ }
20088
+ </div>
20089
+ </div>
20090
+ }
20091
+ }
20092
+ }
20093
+ @for (entry of unexpandableSignal(); track entry.id) {
20094
+ <div class="dbx-pdf-merge-page-unexpandable">
20095
+ <mat-icon class="dbx-pdf-merge-page-unexpandable-icon">lock</mat-icon>
20096
+ <span class="dbx-pdf-merge-page-unexpandable-name dbx-text-truncate" [title]="entry.name">{{ entry.name }}</span>
20097
+ <span class="dbx-hint dbx-small">{{ messageForUnexpandable(entry) }}</span>
20098
+ </div>
20099
+ }
20100
+ `, isInline: true, dependencies: [{ kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: DbxPdfMergePageComponent, selector: "dbx-pdf-merge-page", inputs: ["page"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20101
+ }
20102
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergePageListComponent, decorators: [{
20103
+ type: Component,
20104
+ args: [{
20105
+ selector: 'dbx-pdf-merge-page-list',
20106
+ template: `
20107
+ @if (groupsSignal(); as groups) {
20108
+ @if (groups.length === 0 && unexpandableSignal().length === 0) {
20109
+ <div class="dbx-list-empty-content dbx-hint dbx-p4">No pages yet.</div>
20110
+ } @else {
20111
+ @for (group of groups; track group.groupKey) {
20112
+ <div class="dbx-pdf-merge-page-group">
20113
+ @if (labelForGroup(group); as label) {
20114
+ <div class="dbx-pdf-merge-page-group-label dbx-hint dbx-small">{{ label }}</div>
20115
+ }
20116
+ <div class="dbx-pdf-merge-page-group-items" cdkDropList (cdkDropListDropped)="onDrop(group, $event)">
20117
+ @for (page of group.pages; track page.id) {
20118
+ <dbx-pdf-merge-page [page]="page"></dbx-pdf-merge-page>
20119
+ }
20120
+ </div>
20121
+ </div>
20122
+ }
20123
+ }
20124
+ }
20125
+ @for (entry of unexpandableSignal(); track entry.id) {
20126
+ <div class="dbx-pdf-merge-page-unexpandable">
20127
+ <mat-icon class="dbx-pdf-merge-page-unexpandable-icon">lock</mat-icon>
20128
+ <span class="dbx-pdf-merge-page-unexpandable-name dbx-text-truncate" [title]="entry.name">{{ entry.name }}</span>
20129
+ <span class="dbx-hint dbx-small">{{ messageForUnexpandable(entry) }}</span>
20130
+ </div>
20131
+ }
20132
+ `,
20133
+ host: {
20134
+ class: 'dbx-pdf-merge-page-list d-block'
20135
+ },
20136
+ imports: [CdkDropList, MatIconModule, DbxPdfMergePageComponent],
20137
+ changeDetection: ChangeDetectionStrategy.OnPush,
20138
+ standalone: true
20139
+ }]
20140
+ }], propDecorators: { slotId: [{ type: i0.Input, args: [{ isSignal: true, alias: "slotId", required: false }] }], showGroupLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGroupLabels", required: false }] }] } });
20141
+
18847
20142
  /**
18848
20143
  * Renders a PDF in a fixed-height embed container. Accepts either a URL or a `Blob` source.
18849
20144
  *
@@ -18930,6 +20225,8 @@ class DbxPdfMergeEditorComponent {
18930
20225
  downloadButton = input(...(ngDevMode ? [undefined, { debugName: "downloadButton" }] : /* istanbul ignore next */ []));
18931
20226
  showAddFiles = input(...(ngDevMode ? [undefined, { debugName: "showAddFiles" }] : /* istanbul ignore next */ []));
18932
20227
  showFileList = input(...(ngDevMode ? [undefined, { debugName: "showFileList" }] : /* istanbul ignore next */ []));
20228
+ pageEditing = input(...(ngDevMode ? [undefined, { debugName: "pageEditing" }] : /* istanbul ignore next */ []));
20229
+ sidecar = input(...(ngDevMode ? [undefined, { debugName: "sidecar" }] : /* istanbul ignore next */ []));
18933
20230
  /**
18934
20231
  * Bundles every editor option into one object (see {@link DbxPdfMergeEditorConfig}). Individual inputs override the matching field here, which in turn overrides the workspace-wide {@link DBX_PDF_MERGE_EDITOR_CONFIG} token.
18935
20232
  */
@@ -18943,6 +20240,11 @@ class DbxPdfMergeEditorComponent {
18943
20240
  * Store-level encryption handling default pushed by {@link DbxPdfMergeEditorStoreDirective}. Resolved between the editor's own `config` input and the workspace-wide token in {@link effectiveConfigSignal}.
18944
20241
  */
18945
20242
  storeEncryptedHandlingSignal = toSignal(this.store.encryptedHandling$, { initialValue: undefined });
20243
+ /**
20244
+ * Store-level page-editing and sidecar defaults pushed by {@link DbxPdfMergeEditorStoreDirective}. These read the store's raw (undefaulted) settings so an unset store value still falls through to the workspace-wide token in {@link effectiveConfigSignal}.
20245
+ */
20246
+ storePageEditingSignal = toSignal(this.store.pageEditingSetting$, { initialValue: undefined });
20247
+ storeSidecarSignal = toSignal(this.store.sidecarSetting$, { initialValue: undefined });
18946
20248
  /**
18947
20249
  * Merged config — the editor's own `config` input wins over the store-level default (for `imageCompression`), which in turn wins over the workspace-wide token. The individual inputs are resolved on top of this object in the per-field `*Signal` computeds below.
18948
20250
  */
@@ -18951,6 +20253,8 @@ class DbxPdfMergeEditorComponent {
18951
20253
  const fromToken = this._injectedConfig;
18952
20254
  const storeImageCompression = this.storeImageCompressionSignal();
18953
20255
  const storeEncryptedHandling = this.storeEncryptedHandlingSignal();
20256
+ const storePageEditing = this.storePageEditingSignal();
20257
+ const storeSidecar = this.storeSidecarSignal();
18954
20258
  return {
18955
20259
  imageCompression: fromInput?.imageCompression ?? storeImageCompression ?? fromToken?.imageCompression ?? null,
18956
20260
  outputSizeLimits: fromInput?.outputSizeLimits ?? fromToken?.outputSizeLimits ?? null,
@@ -18962,10 +20266,23 @@ class DbxPdfMergeEditorComponent {
18962
20266
  downloadButton: fromInput?.downloadButton ?? fromToken?.downloadButton,
18963
20267
  showAddFiles: fromInput?.showAddFiles ?? fromToken?.showAddFiles,
18964
20268
  showFileList: fromInput?.showFileList ?? fromToken?.showFileList,
18965
- encryptedHandling: fromInput?.encryptedHandling ?? storeEncryptedHandling ?? fromToken?.encryptedHandling ?? DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING
20269
+ encryptedHandling: fromInput?.encryptedHandling ?? storeEncryptedHandling ?? fromToken?.encryptedHandling ?? DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING,
20270
+ pageEditing: fromInput?.pageEditing ?? storePageEditing ?? fromToken?.pageEditing ?? DEFAULT_DBX_PDF_MERGE_PAGE_EDITING,
20271
+ sidecar: fromInput?.sidecar ?? storeSidecar ?? fromToken?.sidecar ?? DEFAULT_DBX_PDF_MERGE_SIDECAR
18966
20272
  };
18967
20273
  }, ...(ngDevMode ? [{ debugName: "effectiveConfigSignal" }] : /* istanbul ignore next */ []));
18968
20274
  encryptedHandlingSignal = computed(() => this.effectiveConfigSignal().encryptedHandling ?? DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING, ...(ngDevMode ? [{ debugName: "encryptedHandlingSignal" }] : /* istanbul ignore next */ []));
20275
+ /**
20276
+ * Whether page editing is active. When `false` the editor renders its original file list and no page machinery is instantiated at all.
20277
+ */
20278
+ pageEditingSignal = computed(() => {
20279
+ const config = this.effectiveConfigSignal();
20280
+ return this.pageEditing() ?? config.pageEditing ?? DEFAULT_DBX_PDF_MERGE_PAGE_EDITING;
20281
+ }, ...(ngDevMode ? [{ debugName: "pageEditingSignal" }] : /* istanbul ignore next */ []));
20282
+ sidecarSignal = computed(() => {
20283
+ const config = this.effectiveConfigSignal();
20284
+ return this.sidecar() ?? config.sidecar ?? DEFAULT_DBX_PDF_MERGE_SIDECAR;
20285
+ }, ...(ngDevMode ? [{ debugName: "sidecarSignal" }] : /* istanbul ignore next */ []));
18969
20286
  imageCompressionConfigSignal = computed(() => this.effectiveConfigSignal().imageCompression, ...(ngDevMode ? [{ debugName: "imageCompressionConfigSignal" }] : /* istanbul ignore next */ []));
18970
20287
  outputSizeLimitsSignal = computed(() => this.effectiveConfigSignal().outputSizeLimits, ...(ngDevMode ? [{ debugName: "outputSizeLimitsSignal" }] : /* istanbul ignore next */ []));
18971
20288
  // Per-field resolution: dedicated input → config (input/token) → default. The config signal is
@@ -19006,6 +20323,10 @@ class DbxPdfMergeEditorComponent {
19006
20323
  errorBytesSignal = computed(() => this.outputSizeLimitsSignal()?.errorBytes, ...(ngDevMode ? [{ debugName: "errorBytesSignal" }] : /* istanbul ignore next */ []));
19007
20324
  hasReadyEntriesSignal = toSignal(this.store.hasReadyEntries$, { initialValue: false });
19008
20325
  entryCountSignal = toSignal(this.store.entryCount$, { initialValue: 0 });
20326
+ /**
20327
+ * Number of pages that will actually reach the output — the page plan minus anything marked for removal. Only surfaced while page editing is enabled.
20328
+ */
20329
+ mergeablePageCountSignal = toSignal(this.store.mergeablePageCount$, { initialValue: 0 });
19009
20330
  /**
19010
20331
  * Mirrors {@link DbxPdfMergeEditorStore.focusActive$} — `true` while `encryptedHandling === 'focus'` and at least one ready encrypted entry exists. Drives the encrypted-PDF focus banner.
19011
20332
  */
@@ -19075,6 +20396,14 @@ class DbxPdfMergeEditorComponent {
19075
20396
  const handling = this.encryptedHandlingSignal();
19076
20397
  this.store.setEncryptedHandling(handling);
19077
20398
  });
20399
+ effect(() => {
20400
+ const pageEditing = this.pageEditingSignal();
20401
+ this.store.setPageEditing(pageEditing);
20402
+ });
20403
+ effect(() => {
20404
+ const sidecar = this.sidecarSignal();
20405
+ this.store.setSidecar(sidecar);
20406
+ });
19078
20407
  }
19079
20408
  async onFiles(event) {
19080
20409
  const accepted = event.matchResult.accepted;
@@ -19117,18 +20446,20 @@ class DbxPdfMergeEditorComponent {
19117
20446
  });
19118
20447
  }
19119
20448
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19120
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergeEditorComponent, isStandalone: true, selector: "dbx-pdf-merge-editor", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, fileName: { classPropertyName: "fileName", publicName: "fileName", isSignal: true, isRequired: false, transformFunction: null }, showDownloadButton: { classPropertyName: "showDownloadButton", publicName: "showDownloadButton", isSignal: true, isRequired: false, transformFunction: null }, showPreviewButton: { classPropertyName: "showPreviewButton", publicName: "showPreviewButton", isSignal: true, isRequired: false, transformFunction: null }, downloadButton: { classPropertyName: "downloadButton", publicName: "downloadButton", isSignal: true, isRequired: false, transformFunction: null }, showAddFiles: { classPropertyName: "showAddFiles", publicName: "showAddFiles", isSignal: true, isRequired: false, transformFunction: null }, showFileList: { classPropertyName: "showFileList", publicName: "showFileList", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { entriesChanged: "entriesChanged" }, host: { classAttribute: "dbx-pdf-merge-editor d-block" }, ngImport: i0, template: "@if (showAddFilesSignal()) {\n <div class=\"dbx-pdf-merge-editor-upload d-block dbx-mb3\">\n <dbx-file-upload [accept]=\"acceptSignal()\" [multiple]=\"multipleSignal()\" (filesChanged)=\"onFiles($event)\" [hint]=\"'Drop PDFs or images here, or click to browse'\" [text]=\"'Add files'\" icon=\"upload_file\"></dbx-file-upload>\n </div>\n}\n<ng-content></ng-content>\n@if (showFileListSignal()) {\n <dbx-pdf-merge-list></dbx-pdf-merge-list>\n}\n@if (focusActiveSignal()) {\n <div class=\"dbx-pdf-merge-editor-encrypted-banner\">\n <mat-icon>lock</mat-icon>\n <span>Encrypted PDF detected \u2014 only the encrypted file will be used; other files are ignored.</span>\n </div>\n}\n@switch (outputSizeStateSignal()) {\n @case ('warn') {\n <div class=\"dbx-pdf-merge-editor-size-warning\">\n <mat-icon>warning</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }} \u2014 above the recommended {{ formattedWarnLimitSignal() }} limit.</span>\n </div>\n }\n @case ('error') {\n <div class=\"dbx-pdf-merge-editor-size-error\">\n <mat-icon>error</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }}, exceeds the {{ formattedErrorLimitSignal() }} limit. Remove or compress files to continue.</span>\n </div>\n }\n}\n<div class=\"dbx-pdf-merge-editor-actions\">\n <span class=\"dbx-hint dbx-small\">{{ entryCountSignal() }} file(s)</span>\n <span class=\"dbx-spacer\"></span>\n <dbx-button text=\"Clear\" icon=\"delete\" [disabled]=\"entryCountSignal() === 0\" (buttonClick)=\"onClear()\"></dbx-button>\n @if (showPreviewButtonSignal()) {\n <dbx-button text=\"Preview\" icon=\"picture_as_pdf\" [disabled]=\"!canMergeSignal()\" (buttonClick)=\"onPreview()\"></dbx-button>\n }\n @if (showDownloadButtonSignal() && canMergeSignal()) {\n <dbx-download-blob-button [config]=\"downloadConfigSignal()\"></dbx-download-blob-button>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }, { kind: "component", type: DbxFileUploadComponent, selector: "dbx-file-upload", inputs: ["config", "buttonStyle", "buttonDisplay", "mode", "text", "icon", "hint", "clickAreaToUpload"], outputs: ["filesChanged"] }, { kind: "component", type: DbxDownloadBlobButtonComponent, selector: "dbx-download-blob-button", inputs: ["config"] }, { kind: "component", type: DbxPdfMergeListComponent, selector: "dbx-pdf-merge-list" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20449
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergeEditorComponent, isStandalone: true, selector: "dbx-pdf-merge-editor", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, fileName: { classPropertyName: "fileName", publicName: "fileName", isSignal: true, isRequired: false, transformFunction: null }, showDownloadButton: { classPropertyName: "showDownloadButton", publicName: "showDownloadButton", isSignal: true, isRequired: false, transformFunction: null }, showPreviewButton: { classPropertyName: "showPreviewButton", publicName: "showPreviewButton", isSignal: true, isRequired: false, transformFunction: null }, downloadButton: { classPropertyName: "downloadButton", publicName: "downloadButton", isSignal: true, isRequired: false, transformFunction: null }, showAddFiles: { classPropertyName: "showAddFiles", publicName: "showAddFiles", isSignal: true, isRequired: false, transformFunction: null }, showFileList: { classPropertyName: "showFileList", publicName: "showFileList", isSignal: true, isRequired: false, transformFunction: null }, pageEditing: { classPropertyName: "pageEditing", publicName: "pageEditing", isSignal: true, isRequired: false, transformFunction: null }, sidecar: { classPropertyName: "sidecar", publicName: "sidecar", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { entriesChanged: "entriesChanged" }, host: { classAttribute: "dbx-pdf-merge-editor d-block" }, ngImport: i0, template: "@if (showAddFilesSignal()) {\n <div class=\"dbx-pdf-merge-editor-upload d-block dbx-mb3\">\n <dbx-file-upload [accept]=\"acceptSignal()\" [multiple]=\"multipleSignal()\" (filesChanged)=\"onFiles($event)\" [hint]=\"'Drop PDFs or images here, or click to browse'\" [text]=\"'Add files'\" icon=\"upload_file\"></dbx-file-upload>\n </div>\n}\n<ng-content></ng-content>\n@if (showFileListSignal()) {\n @if (pageEditingSignal()) {\n <dbx-pdf-merge-page-list></dbx-pdf-merge-page-list>\n } @else {\n <dbx-pdf-merge-list></dbx-pdf-merge-list>\n }\n}\n@if (focusActiveSignal()) {\n <div class=\"dbx-pdf-merge-editor-encrypted-banner\">\n <mat-icon>lock</mat-icon>\n <span>Encrypted PDF detected \u2014 only the encrypted file will be used; other files are ignored.</span>\n </div>\n}\n@switch (outputSizeStateSignal()) {\n @case ('warn') {\n <div class=\"dbx-pdf-merge-editor-size-warning\">\n <mat-icon>warning</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }} \u2014 above the recommended {{ formattedWarnLimitSignal() }} limit.</span>\n </div>\n }\n @case ('error') {\n <div class=\"dbx-pdf-merge-editor-size-error\">\n <mat-icon>error</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }}, exceeds the {{ formattedErrorLimitSignal() }} limit. Remove or compress files to continue.</span>\n </div>\n }\n}\n<div class=\"dbx-pdf-merge-editor-actions\">\n <span class=\"dbx-hint dbx-small\">{{ entryCountSignal() }} file(s)</span>\n @if (pageEditingSignal()) {\n <span class=\"dbx-hint dbx-small\">{{ mergeablePageCountSignal() }} page(s)</span>\n }\n <span class=\"dbx-spacer\"></span>\n <dbx-button text=\"Clear\" icon=\"delete\" [disabled]=\"entryCountSignal() === 0\" (buttonClick)=\"onClear()\"></dbx-button>\n @if (showPreviewButtonSignal()) {\n <dbx-button text=\"Preview\" icon=\"picture_as_pdf\" [disabled]=\"!canMergeSignal()\" (buttonClick)=\"onPreview()\"></dbx-button>\n }\n @if (showDownloadButtonSignal() && canMergeSignal()) {\n <dbx-download-blob-button [config]=\"downloadConfigSignal()\"></dbx-download-blob-button>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }, { kind: "component", type: DbxFileUploadComponent, selector: "dbx-file-upload", inputs: ["config", "buttonStyle", "buttonDisplay", "mode", "text", "icon", "hint", "clickAreaToUpload"], outputs: ["filesChanged"] }, { kind: "component", type: DbxDownloadBlobButtonComponent, selector: "dbx-download-blob-button", inputs: ["config"] }, { kind: "component", type: DbxPdfMergeListComponent, selector: "dbx-pdf-merge-list" }, { kind: "component", type: DbxPdfMergePageListComponent, selector: "dbx-pdf-merge-page-list", inputs: ["slotId", "showGroupLabels"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19121
20450
  }
19122
20451
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorComponent, decorators: [{
19123
20452
  type: Component,
19124
20453
  args: [{ selector: 'dbx-pdf-merge-editor', host: {
19125
20454
  class: 'dbx-pdf-merge-editor d-block'
19126
- }, imports: [MatIconModule, DbxButtonComponent, DbxFileUploadComponent, DbxDownloadBlobButtonComponent, DbxPdfMergeListComponent], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "@if (showAddFilesSignal()) {\n <div class=\"dbx-pdf-merge-editor-upload d-block dbx-mb3\">\n <dbx-file-upload [accept]=\"acceptSignal()\" [multiple]=\"multipleSignal()\" (filesChanged)=\"onFiles($event)\" [hint]=\"'Drop PDFs or images here, or click to browse'\" [text]=\"'Add files'\" icon=\"upload_file\"></dbx-file-upload>\n </div>\n}\n<ng-content></ng-content>\n@if (showFileListSignal()) {\n <dbx-pdf-merge-list></dbx-pdf-merge-list>\n}\n@if (focusActiveSignal()) {\n <div class=\"dbx-pdf-merge-editor-encrypted-banner\">\n <mat-icon>lock</mat-icon>\n <span>Encrypted PDF detected \u2014 only the encrypted file will be used; other files are ignored.</span>\n </div>\n}\n@switch (outputSizeStateSignal()) {\n @case ('warn') {\n <div class=\"dbx-pdf-merge-editor-size-warning\">\n <mat-icon>warning</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }} \u2014 above the recommended {{ formattedWarnLimitSignal() }} limit.</span>\n </div>\n }\n @case ('error') {\n <div class=\"dbx-pdf-merge-editor-size-error\">\n <mat-icon>error</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }}, exceeds the {{ formattedErrorLimitSignal() }} limit. Remove or compress files to continue.</span>\n </div>\n }\n}\n<div class=\"dbx-pdf-merge-editor-actions\">\n <span class=\"dbx-hint dbx-small\">{{ entryCountSignal() }} file(s)</span>\n <span class=\"dbx-spacer\"></span>\n <dbx-button text=\"Clear\" icon=\"delete\" [disabled]=\"entryCountSignal() === 0\" (buttonClick)=\"onClear()\"></dbx-button>\n @if (showPreviewButtonSignal()) {\n <dbx-button text=\"Preview\" icon=\"picture_as_pdf\" [disabled]=\"!canMergeSignal()\" (buttonClick)=\"onPreview()\"></dbx-button>\n }\n @if (showDownloadButtonSignal() && canMergeSignal()) {\n <dbx-download-blob-button [config]=\"downloadConfigSignal()\"></dbx-download-blob-button>\n }\n</div>\n" }]
19127
- }], ctorParameters: () => [], propDecorators: { accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], fileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileName", required: false }] }], showDownloadButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDownloadButton", required: false }] }], showPreviewButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPreviewButton", required: false }] }], downloadButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadButton", required: false }] }], showAddFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAddFiles", required: false }] }], showFileList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFileList", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], entriesChanged: [{ type: i0.Output, args: ["entriesChanged"] }] } });
20455
+ }, imports: [MatIconModule, DbxButtonComponent, DbxFileUploadComponent, DbxDownloadBlobButtonComponent, DbxPdfMergeListComponent, DbxPdfMergePageListComponent], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "@if (showAddFilesSignal()) {\n <div class=\"dbx-pdf-merge-editor-upload d-block dbx-mb3\">\n <dbx-file-upload [accept]=\"acceptSignal()\" [multiple]=\"multipleSignal()\" (filesChanged)=\"onFiles($event)\" [hint]=\"'Drop PDFs or images here, or click to browse'\" [text]=\"'Add files'\" icon=\"upload_file\"></dbx-file-upload>\n </div>\n}\n<ng-content></ng-content>\n@if (showFileListSignal()) {\n @if (pageEditingSignal()) {\n <dbx-pdf-merge-page-list></dbx-pdf-merge-page-list>\n } @else {\n <dbx-pdf-merge-list></dbx-pdf-merge-list>\n }\n}\n@if (focusActiveSignal()) {\n <div class=\"dbx-pdf-merge-editor-encrypted-banner\">\n <mat-icon>lock</mat-icon>\n <span>Encrypted PDF detected \u2014 only the encrypted file will be used; other files are ignored.</span>\n </div>\n}\n@switch (outputSizeStateSignal()) {\n @case ('warn') {\n <div class=\"dbx-pdf-merge-editor-size-warning\">\n <mat-icon>warning</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }} \u2014 above the recommended {{ formattedWarnLimitSignal() }} limit.</span>\n </div>\n }\n @case ('error') {\n <div class=\"dbx-pdf-merge-editor-size-error\">\n <mat-icon>error</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }}, exceeds the {{ formattedErrorLimitSignal() }} limit. Remove or compress files to continue.</span>\n </div>\n }\n}\n<div class=\"dbx-pdf-merge-editor-actions\">\n <span class=\"dbx-hint dbx-small\">{{ entryCountSignal() }} file(s)</span>\n @if (pageEditingSignal()) {\n <span class=\"dbx-hint dbx-small\">{{ mergeablePageCountSignal() }} page(s)</span>\n }\n <span class=\"dbx-spacer\"></span>\n <dbx-button text=\"Clear\" icon=\"delete\" [disabled]=\"entryCountSignal() === 0\" (buttonClick)=\"onClear()\"></dbx-button>\n @if (showPreviewButtonSignal()) {\n <dbx-button text=\"Preview\" icon=\"picture_as_pdf\" [disabled]=\"!canMergeSignal()\" (buttonClick)=\"onPreview()\"></dbx-button>\n }\n @if (showDownloadButtonSignal() && canMergeSignal()) {\n <dbx-download-blob-button [config]=\"downloadConfigSignal()\"></dbx-download-blob-button>\n }\n</div>\n" }]
20456
+ }], ctorParameters: () => [], propDecorators: { accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], fileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileName", required: false }] }], showDownloadButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDownloadButton", required: false }] }], showPreviewButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPreviewButton", required: false }] }], downloadButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadButton", required: false }] }], showAddFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAddFiles", required: false }] }], showFileList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFileList", required: false }] }], pageEditing: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageEditing", required: false }] }], sidecar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidecar", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], entriesChanged: [{ type: i0.Output, args: ["entriesChanged"] }] } });
19128
20457
 
19129
20458
  /**
19130
20459
  * Directive that registers as the active {@link DbxPdfMergeEditorValidator} on a {@link DbxPdfMergeEditorStore} and gates merge emissions on the readiness of every registered slot. {@link DbxPdfMergeEditorFileUploadComponent} instances walk the injector tree, find this directive, and call {@link registerSlot}/{@link unregisterSlot} during their own lifecycle.
19131
20460
  *
20461
+ * This slot set is intentionally separate from {@link DbxPdfMergeEditorStore.registeredSlotIds$}, and the two should not be merged. This one is keyed by object identity because it exists solely to `combineLatest` each slot's `isValid$`; the store's is keyed by slot id, answers "which sections does this editor declare", and must be populated whether or not a validator is present — which is why it cannot live on this optional directive.
20462
+ *
19132
20463
  * @example
19133
20464
  * ```html
19134
20465
  * <dbx-pdf-merge-editor [showAddFiles]="false" [showFileList]="false">
@@ -19214,6 +20545,10 @@ class DbxPdfMergeEditorFileUploadComponent {
19214
20545
  _validator = inject(DbxPdfMergeEditorFileUploadValidatorDirective, { optional: true });
19215
20546
  _injectedConfig = inject(DBX_PDF_MERGE_EDITOR_CONFIG, { optional: true });
19216
20547
  _preserveEntriesOnDestroy = inject(DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY, { optional: true }) ?? false;
20548
+ /**
20549
+ * The slot id this component registered with the store, captured at registration time so destroy deregisters the same key. `null` before {@link ngOnInit} and after teardown.
20550
+ */
20551
+ _registeredSlotId = null;
19217
20552
  slotId = input.required(...(ngDevMode ? [{ debugName: "slotId" }] : /* istanbul ignore next */ []));
19218
20553
  config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
19219
20554
  acceptSignal = computed(() => this.config()?.accept ?? DEFAULT_PDF_MERGE_ACCEPT, ...(ngDevMode ? [{ debugName: "acceptSignal" }] : /* istanbul ignore next */ []));
@@ -19249,6 +20584,14 @@ class DbxPdfMergeEditorFileUploadComponent {
19249
20584
  */
19250
20585
  ownedEntries$ = toObservable(this.slotId).pipe(switchMap((slotId) => this.store.entriesForSlotId$(slotId)), shareReplay(1));
19251
20586
  ownedEntriesSignal = toSignal(this.ownedEntries$, { initialValue: [] });
20587
+ /**
20588
+ * Whether the shared store has page editing enabled. Drives whether this slot lists its own pages or its own files.
20589
+ */
20590
+ pageEditingSignal = toSignal(this.store.pageEditing$, { initialValue: false });
20591
+ /**
20592
+ * Pages contributed by this slot's entries. Empty while page editing is disabled.
20593
+ */
20594
+ ownedPages$ = toObservable(this.slotId).pipe(switchMap((slotId) => this.store.pagesForSlotId$(slotId)), shareReplay(1));
19252
20595
  /**
19253
20596
  * Whether the slot still has room for more files. Drives visibility of the upload UI — once the slot is at capacity, the uploader is hidden and the user must remove an existing entry to add another.
19254
20597
  */
@@ -19256,7 +20599,7 @@ class DbxPdfMergeEditorFileUploadComponent {
19256
20599
  /**
19257
20600
  * High-level state of the slot — `no_file` when empty, `valid` when owned entries satisfy the slot's thresholds, `invalid` when owned entries fail or are still being validated.
19258
20601
  */
19259
- state$ = this.ownedEntries$.pipe(map((entries) => {
20602
+ state$ = combineLatest([this.ownedEntries$, this.store.pageEditing$, this.ownedPages$]).pipe(map(([entries, pageEditing, pages]) => {
19260
20603
  let state;
19261
20604
  if (entries.length === 0) {
19262
20605
  state = 'no_file';
@@ -19266,6 +20609,10 @@ class DbxPdfMergeEditorFileUploadComponent {
19266
20609
  const readyCount = entries.filter((entry) => entry.status === 'ready').length;
19267
20610
  const minFiles = this.minFilesSignal();
19268
20611
  const maxFiles = this.maxFilesSignal();
20612
+ // Only meaningful once the slot actually has pages: an empty list means editing is off, the
20613
+ // entry is still hydrating, or it could not be expanded (encrypted) — none of which are the
20614
+ // user deleting their own content.
20615
+ const allPagesRemoved = pageEditing && pages.length > 0 && pages.every((page) => page.removed);
19269
20616
  if (validating) {
19270
20617
  state = 'invalid';
19271
20618
  }
@@ -19275,6 +20622,9 @@ class DbxPdfMergeEditorFileUploadComponent {
19275
20622
  else if (maxFiles != null && readyCount > maxFiles) {
19276
20623
  state = 'invalid';
19277
20624
  }
20625
+ else if (allPagesRemoved) {
20626
+ state = 'invalid';
20627
+ }
19278
20628
  else {
19279
20629
  state = 'valid';
19280
20630
  }
@@ -19301,9 +20651,26 @@ class DbxPdfMergeEditorFileUploadComponent {
19301
20651
  }), distinctUntilChanged(), shareReplay(1));
19302
20652
  isValidSignal = toSignal(this.isValid$, { initialValue: false });
19303
20653
  ngOnInit() {
20654
+ // Two registrations, deliberately: the store learns this slot's ID (so consumers like
20655
+ // <dbx-pdf-merge-import> can enumerate the sections this editor declares), while the optional
20656
+ // validator directive tracks the component itself to aggregate `isValid$`. They are separate
20657
+ // because the store is always present while the validator is not, and because the store's
20658
+ // `setValidator` seam exists precisely so it never has to hold slot component references.
20659
+ this._registeredSlotId = this.slotId();
20660
+ this.store.registerSlotId(this._registeredSlotId);
19304
20661
  this._validator?.registerSlot(this);
19305
20662
  }
19306
20663
  ngOnDestroy() {
20664
+ // Unconditional, and deliberately OUTSIDE the `_preserveEntriesOnDestroy` check below: that
20665
+ // token governs whether the slot's ENTRIES survive, not whether the slot is mounted. The
20666
+ // upload dialog sets it to `true` and is torn down on every close, so gating this on it would
20667
+ // leave the store permanently claiming a section that is no longer on screen. The captured id
20668
+ // (rather than a fresh `slotId()` read) guarantees this decrements the same key `ngOnInit`
20669
+ // incremented, and clearing it keeps a repeat destroy from evicting a still-mounted duplicate.
20670
+ if (this._registeredSlotId != null) {
20671
+ this.store.unregisterSlotId(this._registeredSlotId);
20672
+ this._registeredSlotId = null;
20673
+ }
19307
20674
  this._validator?.unregisterSlot(this);
19308
20675
  // Default behavior: removing a slot from a template (e.g. via `@if`) also drops the slot's
19309
20676
  // entries from the store. Opt out by providing `DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY`
@@ -19370,14 +20737,18 @@ class DbxPdfMergeEditorFileUploadComponent {
19370
20737
  }
19371
20738
  @if (ownedEntriesSignal(); as owned) {
19372
20739
  @if (owned.length > 0) {
19373
- <div class="dbx-pdf-merge-editor-file-upload-entries" cdkDropList (cdkDropListDropped)="onDrop($event)">
19374
- @for (entry of owned; track entry.id) {
19375
- <dbx-pdf-merge-entry [entry]="entry"></dbx-pdf-merge-entry>
19376
- }
19377
- </div>
20740
+ @if (pageEditingSignal()) {
20741
+ <dbx-pdf-merge-page-list class="dbx-pdf-merge-editor-file-upload-pages" [slotId]="slotId()"></dbx-pdf-merge-page-list>
20742
+ } @else {
20743
+ <div class="dbx-pdf-merge-editor-file-upload-entries" cdkDropList (cdkDropListDropped)="onDrop($event)">
20744
+ @for (entry of owned; track entry.id) {
20745
+ <dbx-pdf-merge-entry [entry]="entry"></dbx-pdf-merge-entry>
20746
+ }
20747
+ </div>
20748
+ }
19378
20749
  }
19379
20750
  }
19380
- `, isInline: true, dependencies: [{ kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "component", type: DbxFileUploadComponent, selector: "dbx-file-upload", inputs: ["config", "buttonStyle", "buttonDisplay", "mode", "text", "icon", "hint", "clickAreaToUpload"], outputs: ["filesChanged"] }, { kind: "component", type: DbxPdfMergeEntryComponent, selector: "dbx-pdf-merge-entry", inputs: ["entry"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20751
+ `, isInline: true, dependencies: [{ kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "component", type: DbxFileUploadComponent, selector: "dbx-file-upload", inputs: ["config", "buttonStyle", "buttonDisplay", "mode", "text", "icon", "hint", "clickAreaToUpload"], outputs: ["filesChanged"] }, { kind: "component", type: DbxPdfMergeEntryComponent, selector: "dbx-pdf-merge-entry", inputs: ["entry"] }, { kind: "component", type: DbxPdfMergePageListComponent, selector: "dbx-pdf-merge-page-list", inputs: ["slotId", "showGroupLabels"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19381
20752
  }
19382
20753
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorFileUploadComponent, decorators: [{
19383
20754
  type: Component,
@@ -19395,11 +20766,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
19395
20766
  }
19396
20767
  @if (ownedEntriesSignal(); as owned) {
19397
20768
  @if (owned.length > 0) {
19398
- <div class="dbx-pdf-merge-editor-file-upload-entries" cdkDropList (cdkDropListDropped)="onDrop($event)">
19399
- @for (entry of owned; track entry.id) {
19400
- <dbx-pdf-merge-entry [entry]="entry"></dbx-pdf-merge-entry>
19401
- }
19402
- </div>
20769
+ @if (pageEditingSignal()) {
20770
+ <dbx-pdf-merge-page-list class="dbx-pdf-merge-editor-file-upload-pages" [slotId]="slotId()"></dbx-pdf-merge-page-list>
20771
+ } @else {
20772
+ <div class="dbx-pdf-merge-editor-file-upload-entries" cdkDropList (cdkDropListDropped)="onDrop($event)">
20773
+ @for (entry of owned; track entry.id) {
20774
+ <dbx-pdf-merge-entry [entry]="entry"></dbx-pdf-merge-entry>
20775
+ }
20776
+ </div>
20777
+ }
19403
20778
  }
19404
20779
  }
19405
20780
  `,
@@ -19409,7 +20784,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
19409
20784
  '[class.dbx-pdf-merge-editor-file-upload--valid]': 'stateSignal() === "valid"',
19410
20785
  '[class.dbx-pdf-merge-editor-file-upload--no-file]': 'stateSignal() === "no_file"'
19411
20786
  },
19412
- imports: [CdkDropList, DbxFileUploadComponent, DbxPdfMergeEntryComponent],
20787
+ imports: [CdkDropList, DbxFileUploadComponent, DbxPdfMergeEntryComponent, DbxPdfMergePageListComponent],
19413
20788
  changeDetection: ChangeDetectionStrategy.OnPush,
19414
20789
  standalone: true
19415
20790
  }]
@@ -19448,6 +20823,224 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
19448
20823
  }]
19449
20824
  }], propDecorators: { targetState: [{ type: i0.Input, args: [{ isSignal: true, alias: "dbxPdfMergeEditorFileUploadHasState", required: false }] }] } });
19450
20825
 
20826
+ /**
20827
+ * Messages shown for each reason an import can fail.
20828
+ */
20829
+ const DBX_PDF_MERGE_IMPORT_ERROR_MESSAGES = {
20830
+ unreadable: 'This file could not be read as a PDF. It may be corrupt or password protected.',
20831
+ no_sidecar: 'This PDF has no embedded manifest, so its pages cannot be matched back to sections. Choose a PDF that was exported from this editor.',
20832
+ no_documents: 'This PDF has a manifest, but none of its recorded pages could be found in the file.'
20833
+ };
20834
+ /**
20835
+ * Loads a PDF that was previously exported from the editor with an embedded manifest, and repopulates the editor's slots from it.
20836
+ *
20837
+ * This is the payoff of the sidecar: a completed single-file document goes back to being the set of per-slot documents it was assembled from, so a user can replace or edit one section in place instead of rebuilding the whole file. Every entry currently in the store is replaced.
20838
+ *
20839
+ * **Zero configuration.** The sections this editor accepts are read from the `<dbx-pdf-merge-editor-file-upload>` slots mounted against the shared {@link DbxPdfMergeEditorStore} (see {@link DbxPdfMergeEditorStore.registeredSlotIds$}), so the common case needs no bindings at all. A file naming any other section is rejected rather than partially imported — without that check, entries carrying an unrecognized slot id would import and then render nowhere.
20840
+ *
20841
+ * **Renders nothing when no sections are configured.** This component exists to split a document back into slots, so with no slots mounted (and no explicit {@link expectedSlotIds}) it has nothing to import into and collapses to zero size. Bind {@link enforceExpectedSlots} to `false` for the unusual case of an editor that wants the import affordance without declaring slots.
20842
+ *
20843
+ * @example
20844
+ * ```html
20845
+ * <!-- derives its sections from the slots below it -->
20846
+ * <dbx-pdf-merge-import (imported)="onImported($event)"></dbx-pdf-merge-import>
20847
+ * <dbx-pdf-merge-editor>
20848
+ * <dbx-pdf-merge-editor-file-upload slotId="license"></dbx-pdf-merge-editor-file-upload>
20849
+ * <dbx-pdf-merge-editor-file-upload slotId="cert"></dbx-pdf-merge-editor-file-upload>
20850
+ * </dbx-pdf-merge-editor>
20851
+ * ```
20852
+ */
20853
+ class DbxPdfMergeImportComponent {
20854
+ store = inject(DbxPdfMergeEditorStore);
20855
+ config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
20856
+ /**
20857
+ * Optional override for the slot ids this editor renders. Leave unbound to derive them from the slots mounted against the store — the zero-configuration default. When set, a file whose manifest names any other slot is rejected rather than partially imported.
20858
+ *
20859
+ * An explicitly bound empty array reads as "this editor deliberately has no sections" and hides the component, the same as having no slots mounted.
20860
+ */
20861
+ expectedSlotIds = input(...(ngDevMode ? [undefined, { debugName: "expectedSlotIds" }] : /* istanbul ignore next */ []));
20862
+ /**
20863
+ * Whether the imported file's sections must match this editor's. Defaults to `true`.
20864
+ *
20865
+ * Binding `false` is the single escape hatch for an editor with no slots that still wants the import affordance: it both skips the check and keeps the component rendered.
20866
+ */
20867
+ enforceExpectedSlots = input(true, ...(ngDevMode ? [{ debugName: "enforceExpectedSlots" }] : /* istanbul ignore next */ []));
20868
+ imported = output();
20869
+ importFailed = output();
20870
+ /**
20871
+ * Emits the expected sections the imported file did not fill, when there are any. The import itself still succeeds — a missing section is a partial state the user can fill in.
20872
+ */
20873
+ missingSlots = output();
20874
+ _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : /* istanbul ignore next */ []));
20875
+ _result = signal(null, ...(ngDevMode ? [{ debugName: "_result" }] : /* istanbul ignore next */ []));
20876
+ _missingSlotIds = signal([], ...(ngDevMode ? [{ debugName: "_missingSlotIds" }] : /* istanbul ignore next */ []));
20877
+ errorSignal = this._error.asReadonly();
20878
+ resultSignal = this._result.asReadonly();
20879
+ missingSlotIdsSignal = this._missingSlotIds.asReadonly();
20880
+ _registeredSlotIdsSignal = toSignal(this.store.registeredSlotIds$, { initialValue: [] });
20881
+ /**
20882
+ * The sections an imported file is allowed to name, or `null` when no check applies. Resolves the explicit {@link expectedSlotIds} binding over the slots registered with the store.
20883
+ */
20884
+ effectiveExpectedSlotIdsSignal = computed(() => {
20885
+ const registered = this._registeredSlotIdsSignal();
20886
+ const bound = this.expectedSlotIds();
20887
+ let expected;
20888
+ if (!this.enforceExpectedSlots()) {
20889
+ expected = null;
20890
+ }
20891
+ else if (bound == null) {
20892
+ // An empty registry is "nothing declared yet", not "nothing allowed" — the component hides
20893
+ // rather than rejecting, so a null here never becomes a check that fails every file.
20894
+ expected = registered.length > 0 ? registered : null;
20895
+ }
20896
+ else {
20897
+ expected = bound;
20898
+ }
20899
+ return expected;
20900
+ }, ...(ngDevMode ? [{ debugName: "effectiveExpectedSlotIdsSignal" }] : /* istanbul ignore next */ []));
20901
+ /**
20902
+ * Whether this component has anything to import into. `false` collapses it to nothing — see the class docs.
20903
+ */
20904
+ activeSignal = computed(() => {
20905
+ const expected = this.effectiveExpectedSlotIdsSignal();
20906
+ return !this.enforceExpectedSlots() || (expected != null && expected.length > 0);
20907
+ }, ...(ngDevMode ? [{ debugName: "activeSignal" }] : /* istanbul ignore next */ []));
20908
+ acceptSignal = computed(() => this.config()?.accept ?? [PDF_MIME_TYPE], ...(ngDevMode ? [{ debugName: "acceptSignal" }] : /* istanbul ignore next */ []));
20909
+ modeSignal = computed(() => this.config()?.mode ?? 'default', ...(ngDevMode ? [{ debugName: "modeSignal" }] : /* istanbul ignore next */ []));
20910
+ labelSignal = computed(() => this.config()?.label, ...(ngDevMode ? [{ debugName: "labelSignal" }] : /* istanbul ignore next */ []));
20911
+ hintSignal = computed(() => this.config()?.hint ?? 'Choose a PDF previously exported from this editor', ...(ngDevMode ? [{ debugName: "hintSignal" }] : /* istanbul ignore next */ []));
20912
+ textSignal = computed(() => this.config()?.text ?? 'Import merged PDF', ...(ngDevMode ? [{ debugName: "textSignal" }] : /* istanbul ignore next */ []));
20913
+ iconSignal = computed(() => this.config()?.icon ?? 'upload_file', ...(ngDevMode ? [{ debugName: "iconSignal" }] : /* istanbul ignore next */ []));
20914
+ successLabelSignal = computed(() => {
20915
+ const result = this._result();
20916
+ let label;
20917
+ if (result == null) {
20918
+ label = null;
20919
+ }
20920
+ else {
20921
+ const sections = result.slotIds.map((slotId) => slotId ?? 'unsectioned').join(', ');
20922
+ label = `Imported ${result.entries.length} section(s): ${sections}.`;
20923
+ }
20924
+ return label;
20925
+ }, ...(ngDevMode ? [{ debugName: "successLabelSignal" }] : /* istanbul ignore next */ []));
20926
+ /**
20927
+ * Warning shown when the imported file left one or more expected sections empty. The import succeeded — this only explains why a slot is still blank.
20928
+ */
20929
+ missingLabelSignal = computed(() => {
20930
+ const missing = this._missingSlotIds();
20931
+ return missing.length > 0 ? `This PDF did not include section(s): ${missing.join(', ')}.` : null;
20932
+ }, ...(ngDevMode ? [{ debugName: "missingLabelSignal" }] : /* istanbul ignore next */ []));
20933
+ async onFiles(event) {
20934
+ const file = event.matchResult.accepted[0];
20935
+ if (file != null) {
20936
+ this._error.set(null);
20937
+ this._result.set(null);
20938
+ this._missingSlotIds.set([]);
20939
+ // Delegates to the store so the picker and a programmatic import share one implementation
20940
+ // and one set of error semantics. The slot check is opt-in on the store, so the component's
20941
+ // own resolved expectation is what preserves the picker's behavior. The returned state is
20942
+ // used rather than `importState$` so these outputs only ever describe a picked file.
20943
+ const state = await this.store.importMergedPdf({ source: file, expectedSlotIds: this.effectiveExpectedSlotIdsSignal() });
20944
+ if (state.status === 'failed') {
20945
+ this.fail(this.messageForFailure(state));
20946
+ }
20947
+ else if (state.result != null) {
20948
+ const { result } = state;
20949
+ this._result.set(result);
20950
+ this._missingSlotIds.set(result.missingSlotIds);
20951
+ this.imported.emit(result);
20952
+ if (result.missingSlotIds.length > 0) {
20953
+ this.missingSlots.emit(result.missingSlotIds);
20954
+ }
20955
+ }
20956
+ }
20957
+ }
20958
+ messageForFailure(state) {
20959
+ const unexpected = state.unexpectedSlotIds ?? [];
20960
+ let message;
20961
+ if (state.error === 'unexpected_slots') {
20962
+ message = `This PDF contains section(s) this editor does not have: ${unexpected.map((slotId) => slotId ?? 'unsectioned').join(', ')}.`;
20963
+ }
20964
+ else {
20965
+ message = DBX_PDF_MERGE_IMPORT_ERROR_MESSAGES[state.error ?? 'unreadable'];
20966
+ }
20967
+ return message;
20968
+ }
20969
+ fail(message) {
20970
+ this._error.set(message);
20971
+ this.importFailed.emit(message);
20972
+ }
20973
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeImportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20974
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergeImportComponent, isStandalone: true, selector: "dbx-pdf-merge-import", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, expectedSlotIds: { classPropertyName: "expectedSlotIds", publicName: "expectedSlotIds", isSignal: true, isRequired: false, transformFunction: null }, enforceExpectedSlots: { classPropertyName: "enforceExpectedSlots", publicName: "enforceExpectedSlots", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imported: "imported", importFailed: "importFailed", missingSlots: "missingSlots" }, host: { properties: { "class.d-block": "activeSignal()", "class.dbx-mb3": "activeSignal()" }, classAttribute: "dbx-pdf-merge-import" }, ngImport: i0, template: `
20975
+ @if (activeSignal()) {
20976
+ @if (labelSignal(); as label) {
20977
+ <div class="dbx-pdf-merge-import-label">{{ label }}</div>
20978
+ }
20979
+ <dbx-file-upload [accept]="acceptSignal()" [multiple]="false" [mode]="modeSignal()" [hint]="hintSignal()" [text]="textSignal()" [icon]="iconSignal()" (filesChanged)="onFiles($event)"></dbx-file-upload>
20980
+ @if (errorSignal(); as error) {
20981
+ <div class="dbx-pdf-merge-import-error">
20982
+ <mat-icon class="dbx-pdf-merge-import-icon">error</mat-icon>
20983
+ <span>{{ error }}</span>
20984
+ </div>
20985
+ }
20986
+ @if (successLabelSignal(); as success) {
20987
+ <div class="dbx-pdf-merge-import-success">
20988
+ <mat-icon class="dbx-pdf-merge-import-icon">check_circle</mat-icon>
20989
+ <span>{{ success }}</span>
20990
+ </div>
20991
+ }
20992
+ @if (missingLabelSignal(); as missing) {
20993
+ <div class="dbx-pdf-merge-import-warning">
20994
+ <mat-icon class="dbx-pdf-merge-import-icon">warning</mat-icon>
20995
+ <span>{{ missing }}</span>
20996
+ </div>
20997
+ }
20998
+ }
20999
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: DbxFileUploadComponent, selector: "dbx-file-upload", inputs: ["config", "buttonStyle", "buttonDisplay", "mode", "text", "icon", "hint", "clickAreaToUpload"], outputs: ["filesChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
21000
+ }
21001
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeImportComponent, decorators: [{
21002
+ type: Component,
21003
+ args: [{
21004
+ selector: 'dbx-pdf-merge-import',
21005
+ template: `
21006
+ @if (activeSignal()) {
21007
+ @if (labelSignal(); as label) {
21008
+ <div class="dbx-pdf-merge-import-label">{{ label }}</div>
21009
+ }
21010
+ <dbx-file-upload [accept]="acceptSignal()" [multiple]="false" [mode]="modeSignal()" [hint]="hintSignal()" [text]="textSignal()" [icon]="iconSignal()" (filesChanged)="onFiles($event)"></dbx-file-upload>
21011
+ @if (errorSignal(); as error) {
21012
+ <div class="dbx-pdf-merge-import-error">
21013
+ <mat-icon class="dbx-pdf-merge-import-icon">error</mat-icon>
21014
+ <span>{{ error }}</span>
21015
+ </div>
21016
+ }
21017
+ @if (successLabelSignal(); as success) {
21018
+ <div class="dbx-pdf-merge-import-success">
21019
+ <mat-icon class="dbx-pdf-merge-import-icon">check_circle</mat-icon>
21020
+ <span>{{ success }}</span>
21021
+ </div>
21022
+ }
21023
+ @if (missingLabelSignal(); as missing) {
21024
+ <div class="dbx-pdf-merge-import-warning">
21025
+ <mat-icon class="dbx-pdf-merge-import-icon">warning</mat-icon>
21026
+ <span>{{ missing }}</span>
21027
+ </div>
21028
+ }
21029
+ }
21030
+ `,
21031
+ host: {
21032
+ class: 'dbx-pdf-merge-import',
21033
+ // Collapsed rather than merely emptied while inactive: without `d-block` the host is an empty
21034
+ // inline element of zero size, and dropping the margin keeps it from occupying layout.
21035
+ '[class.d-block]': 'activeSignal()',
21036
+ '[class.dbx-mb3]': 'activeSignal()'
21037
+ },
21038
+ imports: [MatIconModule, DbxFileUploadComponent],
21039
+ changeDetection: ChangeDetectionStrategy.OnPush,
21040
+ standalone: true
21041
+ }]
21042
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], expectedSlotIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "expectedSlotIds", required: false }] }], enforceExpectedSlots: [{ type: i0.Input, args: [{ isSignal: true, alias: "enforceExpectedSlots", required: false }] }], imported: [{ type: i0.Output, args: ["imported"] }], importFailed: [{ type: i0.Output, args: ["importFailed"] }], missingSlots: [{ type: i0.Output, args: ["missingSlots"] }] } });
21043
+
19451
21044
  const DEFAULT_UPLOAD_FOOTER_ICON = 'cloud_upload';
19452
21045
  const DEFAULT_UPLOAD_FOOTER_TEXT = 'Upload';
19453
21046
  const DEFAULT_UPLOAD_FOOTER_COLOR = 'primary';
@@ -19509,7 +21102,7 @@ class DbxPdfMergeUploadDialogComponent extends AbstractDialogDirective {
19509
21102
  </div>
19510
21103
  }
19511
21104
  </dbx-dialog-content>
19512
- `, isInline: true, dependencies: [{ kind: "directive", type: DbxDialogContentDirective, selector: "dbx-dialog-content,[dbxDialogContent],.dbx-dialog-content", inputs: ["width"] }, { kind: "component", type: DbxDialogContentCloseComponent, selector: "dbx-dialog-content-close", inputs: ["padded"], outputs: ["close"] }, { kind: "component", type: DbxInjectionComponent, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: ["config", "template"] }, { kind: "component", type: DbxPdfMergeEditorComponent, selector: "dbx-pdf-merge-editor", inputs: ["accept", "multiple", "fileName", "showDownloadButton", "showPreviewButton", "downloadButton", "showAddFiles", "showFileList", "config"], outputs: ["entriesChanged"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
21105
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxDialogContentDirective, selector: "dbx-dialog-content,[dbxDialogContent],.dbx-dialog-content", inputs: ["width"] }, { kind: "component", type: DbxDialogContentCloseComponent, selector: "dbx-dialog-content-close", inputs: ["padded"], outputs: ["close"] }, { kind: "component", type: DbxInjectionComponent, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: ["config", "template"] }, { kind: "component", type: DbxPdfMergeEditorComponent, selector: "dbx-pdf-merge-editor", inputs: ["accept", "multiple", "fileName", "showDownloadButton", "showPreviewButton", "downloadButton", "showAddFiles", "showFileList", "pageEditing", "sidecar", "config"], outputs: ["entriesChanged"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19513
21106
  }
19514
21107
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeUploadDialogComponent, decorators: [{
19515
21108
  type: Component,
@@ -19712,5 +21305,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
19712
21305
  * Generated bundle index. Do not edit.
19713
21306
  */
19714
21307
 
19715
- export { APP_POPUP_MINIMIZED_WIDTH, APP_POPUP_NORMAL_HEIGHT, APP_POPUP_NORMAL_WIDTH, AbstractDbxClipboardDirective, AbstractDbxErrorWidgetComponent, AbstractDbxFileUploadComponent, AbstractDbxHelpWidgetDirective, AbstractDbxListAccordionViewDirective, AbstractDbxListGridViewDirective, AbstractDbxListViewDirective, AbstractDbxListWrapperDirective, AbstractDbxPartialPresetFilterMenuDirective, AbstractDbxSegueAnchorDirective, AbstractDbxSelectionListViewDirective, AbstractDbxSelectionListWrapperDirective, AbstractDbxValueListItemModifierDirective, AbstractDbxValueListViewDirective, AbstractDbxValueListViewItemComponent, AbstractDbxWidgetComponent, AbstractDialogDirective, AbstractFilterPopoverButtonDirective, AbstractPopoverDirective, AbstractPopoverRefDirective, AbstractPopoverRefWithEventsDirective, AbstractPopupDirective, AbstractPromptConfirmDirective, CompactContextStore, CompactMode, DBX_ACTION_SNACKBAR_DEFAULTS, DBX_ACTION_SNACKBAR_SERVICE_CONFIG, DBX_AVATAR_CONTEXT_DATA_TOKEN, DBX_COLOR_CUSTOM_BG_CSS_CLASS, DBX_COLOR_CUSTOM_TEXT_CSS_CLASS, DBX_CURATED_COLOR_COUNT, DBX_CURATED_COLOR_TEMPLATES, DBX_CURATED_COLOR_TEMPLATE_KEY_PREFIX, DBX_DARK_STYLE_CLASS_SUFFIX, DBX_HELP_WIDGET_ENTRY_DATA_TOKEN, DBX_LIST_ACCORDION_VIEW_COMPONENT_IMPORTS_AND_EXPORTS, DBX_LIST_GRID_VIEW_COMPONENT_IMPORTS_AND_EXPORTS, DBX_LIST_ITEM_DISABLE_RIPPLE_LIST_ITEM_MODIFIER_KEY, DBX_LIST_ITEM_IS_SELECTED_ITEM_MODIFIER_KEY, DBX_MODEL_VIEW_TRACKER_STORAGE_ACCESSOR_TOKEN, DBX_PDF_MERGE_EDITOR_CONFIG, DBX_PDF_MERGE_EDITOR_INITIAL_STATE, DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY, DBX_PDF_MERGE_ENCRYPTED_ERROR_MESSAGE, DBX_PROGRESS_BUTTON_GLOBAL_CONFIG, DBX_ROUTER_ANCHOR_COMPONENTS, DBX_ROUTER_VALUE_LIST_ITEM_MODIFIER_KEY, DBX_THEME_COLORS, DBX_THEME_COLORS_EXTRA, DBX_THEME_COLORS_EXTRA_SECONDARY, DBX_THEME_COLORS_MAIN, DBX_VALUE_LIST_VIEW_ITEM, DBX_WEB_FILE_PREVIEW_SERVICE_ENTRIES_TOKEN, DBX_WEB_FILE_PREVIEW_SERVICE_ZIP_COMPONENT_PRESET, DBX_WEB_FILE_PREVIEW_SERVICE_ZIP_PRESET_ENTRY, DBX_WEB_PAGE_TITLE_SERVICE_CONFIG, DEFAULT_DBX_CHIP_TONE, DEFAULT_DBX_DETACH_KEY, DEFAULT_DBX_ERROR_SNACKBAR_CONFIG, DEFAULT_DBX_HELP_VIEW_POPOVER_KEY, DEFAULT_DBX_LINKIFY_STRING_TYPE, DEFAULT_DBX_LIST_ACCORDION_VIEW_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_LIST_GRID_VIEW_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_LIST_ITEM_DISABLE_FUNCTION, DEFAULT_DBX_LIST_ITEM_IS_SELECTED_FUNCTION, DEFAULT_DBX_LIST_SCROLL_DISTANCE, DEFAULT_DBX_LIST_THROTTLE_SCROLL, DEFAULT_DBX_LIST_VIEW_META_ICON, DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING, DEFAULT_DBX_PROMPT_CONFIRM_DIALOG_CONFIG, DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_SIDENAV_MENU_ICON, DEFAULT_DBX_STYLE_CONFIG_TOKEN, DEFAULT_DBX_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_WEB_FILE_PREVIEW_SERVICE_DIALOG_WITH_COMPONENT_FUNCTION, DEFAULT_DBX_WEB_FILE_PREVIEW_SERVICE_PREVIEW_COMPONENT_FUNCTION, DEFAULT_ERROR_POPOVER_KEY, DEFAULT_ERROR_WIDGET_CODE, DEFAULT_FILTER_POPOVER_KEY, DEFAULT_IMAGE_BITMAP_TO_BLOB_ENCODER, DEFAULT_IMAGE_JPEG_QUALITY, DEFAULT_LIST_GRID_SIZE_CONFIG, DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_LOADING_PROGRESS_DIAMETER, DEFAULT_PDF_MERGE_ACCEPT, DEFAULT_SCREEN_MEDIA_SERVICE_CONFIG, DEFAULT_SNACKBAR_DIRECTIVE_DURATION, DEFAULT_TWO_COLUMNS_MIN_RIGHT_WIDTH, DEFAULT_VALUE_LIST_VIEW_CONTENT_COMPONENT_TRACK_BY_FUNCTION, DbxAccordionHeaderHeightDirective, DbxActionConfirmDirective, DbxActionDialogDirective, DbxActionErrorDirective, DbxActionKeyTriggerDirective, DbxActionLoadingContextDirective, DbxActionModule, DbxActionPopoverDirective, DbxActionSnackbarComponent, DbxActionSnackbarDirective, DbxActionSnackbarErrorDirective, DbxActionSnackbarModule, DbxActionSnackbarService, DbxActionTransitionSafetyDirective, DbxActionUIRouterTransitionSafetyDialogComponent, DbxAnchorComponent, DbxAnchorContentComponent, DbxAnchorIconComponent, DbxAnchorListComponent, DbxAngularRouterSegueAnchorComponent, DbxAvatarComponent, DbxAvatarViewService, DbxAvatarViewServiceConfig, DbxBarDirective, DbxBarHeaderComponent, DbxBarLayoutModule, DbxBasicLoadingComponent, DbxBodyDirective, DbxButtonComponent, DbxButtonModule, DbxButtonSpacerDirective, DbxCardBoxComponent, DbxCardBoxContainerDirective, DbxCardBoxLayoutModule, DbxChipDirective, DbxChipListComponent, DbxClickToCopyTextComponent, DbxClickToCopyTextDirective, DbxColorDirective, DbxColorService, DbxColorServiceConfig, DbxColumnLayoutModule, DbxCompactDirective, DbxCompactLayoutModule, DbxContentBorderDirective, DbxContentBoxDirective, DbxContentContainerDirective, DbxContentDirective, DbxContentElevateDirective, DbxContentLayoutModule, DbxContentPageDirective, DbxContentPitDirective, DbxDetachContentComponent, DbxDetachControlButtonsComponent, DbxDetachController, DbxDetachControlsComponent, DbxDetachInitDirective, DbxDetachInteractionModule, DbxDetachOutletComponent, DbxDetachOverlayComponent, DbxDetachService, DbxDetachWindowState, DbxDetailBlockComponent, DbxDetailBlockHeaderComponent, DbxDialogContentCloseComponent, DbxDialogContentDirective, DbxDialogContentFooterComponent, DbxDialogInteractionModule, DbxDialogModule, DbxDownloadBlobButtonComponent, DbxDownloadTextViewComponent, DbxEmbedComponent, DbxErrorComponent, DbxErrorDefaultErrorWidgetComponent, DbxErrorDetailsComponent, DbxErrorPopoverComponent, DbxErrorSnackbarComponent, DbxErrorSnackbarService, DbxErrorViewComponent, DbxErrorWidgetService, DbxErrorWidgetViewComponent, DbxFileUploadActionCompatable, DbxFileUploadActionSyncDirective, DbxFileUploadAreaComponent, DbxFileUploadButtonComponent, DbxFileUploadComponent, DbxFilterInteractionModule, DbxFilterPopoverButtonComponent, DbxFilterPopoverComponent, DbxFilterWrapperComponent, DbxFlagComponent, DbxFlagLayoutModule, DbxFlagPromptComponent, DbxFlexGroupDirective, DbxFlexLayoutModule, DbxFlexSizeDirective, DbxHelpContextDirective, DbxHelpContextService, DbxHelpViewListComponent, DbxHelpViewListEntryComponent, DbxHelpViewPopoverButtonComponent, DbxHelpViewPopoverComponent, DbxHelpWidgetService, DbxHelpWidgetServiceConfig, DbxIconButtonComponent, DbxIconButtonModule, DbxIconItemComponent, DbxIconSpacerDirective, DbxIconTileComponent, DbxIconTileDirective, DbxIfSidenavDisplayModeDirective, DbxIframeComponent, DbxInjectionDialogComponent, DbxInteractionModule, DbxIntroActionSectionComponent, DbxLabelBlockComponent, DbxLayoutModule, DbxLinkComponent, DbxLinkifyComponent, DbxLinkifyService, DbxLinkifyServiceConfig, DbxListAccordionViewComponentImportsModule, DbxListComponent, DbxListEmptyContentComponent, DbxListGridViewComponentImportsModule, DbxListInternalContentDirective, DbxListItemAnchorModifierDirective, DbxListItemDisableRippleModifierDirective, DbxListItemIsSelectedModifierDirective, DbxListModifierModule, DbxListModule, DbxListTitleGroupDirective, DbxListView, DbxListViewMetaIconComponent, DbxListViewWrapper, DbxListWrapperComponentImportsModule, DbxLoadingComponent, DbxLoadingErrorDirective, DbxLoadingModule, DbxLoadingProgressComponent, DbxModelObjectStateService, actions as DbxModelStateActions, model_actions as DbxModelStateModelActions, DbxModelTrackerService, DbxModelTypesService, DbxModelViewTrackerStorage, DbxNavbarComponent, DbxNumberWithLimitComponent, DbxOneColumnComponent, DbxOneColumnLayoutModule, DbxPagebarComponent, DbxPartialPresetFilterListComponent, DbxPartialPresetFilterMenuComponent, DbxPdfMergeEditorComponent, DbxPdfMergeEditorFileUploadComponent, DbxPdfMergeEditorFileUploadHasStateDirective, DbxPdfMergeEditorFileUploadValidatorDirective, DbxPdfMergeEditorStore, DbxPdfMergeEditorStoreDirective, DbxPdfMergeEntryComponent, DbxPdfMergeListComponent, DbxPdfMergeUploadActionDirective, DbxPdfMergeUploadButtonDirective, DbxPdfMergeUploadDialogComponent, DbxPdfPreviewComponent, DbxPopoverCloseButtonComponent, DbxPopoverComponent, DbxPopoverComponentController, DbxPopoverContentComponent, DbxPopoverController, DbxPopoverControlsDirective, DbxPopoverCoordinatorComponent, DbxPopoverCoordinatorService, DbxPopoverHeaderComponent, DbxPopoverInteractionContentModule, DbxPopoverInteractionModule, DbxPopoverScrollContentDirective, DbxPopoverService, DbxPopupComponent, DbxPopupComponentController, DbxPopupContentComponent, DbxPopupControlButtonsComponent, DbxPopupController, DbxPopupControlsComponent, DbxPopupCoordinatorComponent, DbxPopupCoordinatorService, DbxPopupInteractionModule, DbxPopupService, DbxPopupWindowState, DbxPresetFilterListComponent, DbxPresetFilterMenuComponent, DbxProgressBarButtonComponent, DbxProgressButtonsModule, DbxProgressSpinnerButtonComponent, DbxPromptBoxDirective, DbxPromptComponent, DbxPromptConfirm, DbxPromptConfirmButtonDirective, DbxPromptConfirmComponent, DbxPromptConfirmDialogComponent, DbxPromptConfirmDirective, DbxPromptModule, DbxPromptPageComponent, DbxReadableErrorModule, DbxResizedDirective, DbxRouterAnchorModule, DbxRouterLayoutModule, DbxRouterSidenavModule, DbxRouterWebProviderConfig, DbxScreenMediaService, DbxScreenMediaServiceConfig, DbxSectionComponent, DbxSectionHeaderComponent, DbxSectionLayoutModule, DbxSectionPageComponent, DbxSelectionValueListViewComponent, DbxSelectionValueListViewComponentImportsModule, DbxSelectionValueListViewContentComponent, DbxSetStyleDirective, DbxSidenavButtonComponent, DbxSidenavComponent, DbxSidenavPageComponent, DbxSidenavPagebarComponent, DbxSpacerDirective, DbxStepBlockComponent, DbxStructureDirective, DbxStructureModule, DbxStyleBodyDirective, DbxStyleDirective, DbxStyleLayoutModule, DbxStyleService, DbxSubSectionComponent, DbxTextChipsComponent, DbxTextColorDirective, DbxTextModule, DbxTwoBlockComponent, DbxTwoColumnBackDirective, DbxTwoColumnColumnHeadDirective, DbxTwoColumnComponent, DbxTwoColumnContextDirective, DbxTwoColumnFullLeftDirective, DbxTwoColumnLayoutModule, DbxTwoColumnRightComponent, DbxTwoColumnSrefDirective, DbxTwoColumnSrefShowRightDirective, DbxUIRouterSegueAnchorComponent, DbxUnitedStatesAddressComponent, DbxValueListAccordionViewComponent, DbxValueListAccordionViewContentComponent, DbxValueListAccordionViewContentGroupComponent, DbxValueListGridSizeDirective, DbxValueListGridViewComponent, DbxValueListGridViewContentComponent, DbxValueListGridViewContentGroupComponent, DbxValueListItemModifier, DbxValueListItemModifierDirective, DbxValueListView, DbxValueListViewComponent, DbxValueListViewComponentImportsModule, DbxValueListViewContentComponent, DbxValueListViewContentGroupComponent, DbxValueListViewGroupDelegate, DbxWebFilePreviewComponent, DbxWebFilePreviewService, DbxWebModule, DbxWebPageTitleInfoDirective, DbxWebPageTitleService, DbxWidgetListGridComponent, DbxWidgetListGridViewComponent, DbxWidgetListGridViewItemComponent, DbxWidgetService, DbxWidgetViewComponent, DbxWindowKeyDownListenerDirective, DbxZipBlobPreviewComponent, DbxZipPreviewComponent, PDF_MERGE_RESULT_MIME_TYPE, PopoverPositionStrategy, PopupGlobalPositionStrategy, SCREEN_MEDIA_WIDTH_TYPE_SIZE_MAP, SIDE_NAV_DISPLAY_MODE_ORDER, SideNavDisplayMode, TRACK_BY_MODEL_ID, TRACK_BY_MODEL_KEY, TwoColumnsContextStore, UNKNOWN_ERROR_WIDGET_CODE, addConfigToValueListItems, allDbxModelViewTrackerEventModelKeys, allDbxModelViewTrackerEventSetModelKeys, buildPdfMergeEntry, buildPdfMergeEntrySync, catchErrorServerParams, classifyPdfMergeFile, compactModeFromInput, compareScreenMediaWidthTypes, compressImageFile, convertServerErrorParams, convertToPOJOServerErrorResponse, convertToServerErrorResponse, copyToClipboardFunction, dbxColorBackground, dbxCuratedColorConfigForString, dbxListAccordionViewComponentImportsAndExports, dbxListGridViewComponentImportsAndExports, dbxPresetFilterMenuButtonIconObservable, dbxPresetFilterMenuButtonTextObservable, dbxStyleClassCleanSuffix, dbxThemeColorCssToken, dbxThemeColorCssTokenVar, dbxThemeColorCssVariable, dbxThemeColorCssVariableVar, dbxValueListItemDecisionFunction, dbxValueListItemKeyForItemValue, dbxWebDefaultPageTitleDelegate, dbxZipBlobPreviewEntryTreeFromEntries, defaultDbxModelViewTrackerStorageAccessorFactory, defaultDbxValueListViewGroupDelegate, defaultDbxValueListViewGroupValuesFunction, disableRightClickInCdkBackdrop, fileAcceptFilterTypeStringArray, fileAcceptFunction, fileAcceptString, fileArrayAcceptMatchFunction, flattenAccordionGroups, formatPdfMergeEntrySize, index as fromDbxModel, injectCopyToClipboardFunction, injectCopyToClipboardFunctionWithSnackbarMessage, isDbxColorConfig, listItemModifier, makeDbxActionSnackbarDisplayConfigGeneratorFunction, mapCompactModeObs, mapValuesToValuesListItemConfigObs, mergePdfMergeEntries, index$1 as onDbxModel, openEmbedDialog, openIframeDialog, openPdfPreviewDialog, openZipPreviewDialog, overrideClickElementEffect, provideDbxDetachController, provideDbxFileUploadActionCompatable, provideDbxHelpServices, provideDbxLinkify, provideDbxListView, provideDbxListViewWrapper, provideDbxModelService, provideDbxPdfMergeEditorConfig, provideDbxPdfMergeEditorPreserveEntriesOnSlotDestroy, provideDbxProgressButtonGlobalConfig, provideDbxPromptConfirm, provideDbxRouterWebAngularRouterProviderConfig, provideDbxRouterWebUiRouterProviderConfig, provideDbxScreenMediaService, provideDbxStyleService, provideDbxValueListView, provideDbxValueListViewGroupDelegate, provideDbxValueListViewModifier, provideDbxWebFilePreviewServiceEntries, provideDbxWebPageTitleService, provideTwoColumnsContext, registerHelpContextKeysWithDbxHelpContextService, resizeSignal, resolveSideNavDisplayMode, sanitizeDbxDialogContentConfig, screenMediaWidthTypeIsActive, trackByModelKeyRef, trackByUniqueIdentifier, validatePdfMergeEntry };
21308
+ export { APP_POPUP_MINIMIZED_WIDTH, APP_POPUP_NORMAL_HEIGHT, APP_POPUP_NORMAL_WIDTH, AbstractDbxClipboardDirective, AbstractDbxErrorWidgetComponent, AbstractDbxFileUploadComponent, AbstractDbxHelpWidgetDirective, AbstractDbxListAccordionViewDirective, AbstractDbxListGridViewDirective, AbstractDbxListViewDirective, AbstractDbxListWrapperDirective, AbstractDbxPartialPresetFilterMenuDirective, AbstractDbxSegueAnchorDirective, AbstractDbxSelectionListViewDirective, AbstractDbxSelectionListWrapperDirective, AbstractDbxValueListItemModifierDirective, AbstractDbxValueListViewDirective, AbstractDbxValueListViewItemComponent, AbstractDbxWidgetComponent, AbstractDialogDirective, AbstractFilterPopoverButtonDirective, AbstractPopoverDirective, AbstractPopoverRefDirective, AbstractPopoverRefWithEventsDirective, AbstractPopupDirective, AbstractPromptConfirmDirective, CompactContextStore, CompactMode, DBX_ACTION_SNACKBAR_DEFAULTS, DBX_ACTION_SNACKBAR_SERVICE_CONFIG, DBX_AVATAR_CONTEXT_DATA_TOKEN, DBX_COLOR_CUSTOM_BG_CSS_CLASS, DBX_COLOR_CUSTOM_TEXT_CSS_CLASS, DBX_CURATED_COLOR_COUNT, DBX_CURATED_COLOR_TEMPLATES, DBX_CURATED_COLOR_TEMPLATE_KEY_PREFIX, DBX_DARK_STYLE_CLASS_SUFFIX, DBX_HELP_WIDGET_ENTRY_DATA_TOKEN, DBX_LIST_ACCORDION_VIEW_COMPONENT_IMPORTS_AND_EXPORTS, DBX_LIST_GRID_VIEW_COMPONENT_IMPORTS_AND_EXPORTS, DBX_LIST_ITEM_DISABLE_RIPPLE_LIST_ITEM_MODIFIER_KEY, DBX_LIST_ITEM_IS_SELECTED_ITEM_MODIFIER_KEY, DBX_MODEL_VIEW_TRACKER_STORAGE_ACCESSOR_TOKEN, DBX_PDF_MERGE_EDITOR_CONFIG, DBX_PDF_MERGE_EDITOR_INITIAL_STATE, DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY, DBX_PDF_MERGE_ENCRYPTED_ERROR_MESSAGE, DBX_PDF_MERGE_ENCRYPTED_NOT_EDITABLE_MESSAGE, DBX_PDF_MERGE_IMPORT_ERROR_MESSAGES, DBX_PROGRESS_BUTTON_GLOBAL_CONFIG, DBX_ROUTER_ANCHOR_COMPONENTS, DBX_ROUTER_VALUE_LIST_ITEM_MODIFIER_KEY, DBX_THEME_COLORS, DBX_THEME_COLORS_EXTRA, DBX_THEME_COLORS_EXTRA_SECONDARY, DBX_THEME_COLORS_MAIN, DBX_VALUE_LIST_VIEW_ITEM, DBX_WEB_FILE_PREVIEW_SERVICE_ENTRIES_TOKEN, DBX_WEB_FILE_PREVIEW_SERVICE_ZIP_COMPONENT_PRESET, DBX_WEB_FILE_PREVIEW_SERVICE_ZIP_PRESET_ENTRY, DBX_WEB_PAGE_TITLE_SERVICE_CONFIG, DEFAULT_DBX_CHIP_TONE, DEFAULT_DBX_DETACH_KEY, DEFAULT_DBX_ERROR_SNACKBAR_CONFIG, DEFAULT_DBX_HELP_VIEW_POPOVER_KEY, DEFAULT_DBX_LINKIFY_STRING_TYPE, DEFAULT_DBX_LIST_ACCORDION_VIEW_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_LIST_GRID_VIEW_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_LIST_ITEM_DISABLE_FUNCTION, DEFAULT_DBX_LIST_ITEM_IS_SELECTED_FUNCTION, DEFAULT_DBX_LIST_SCROLL_DISTANCE, DEFAULT_DBX_LIST_THROTTLE_SCROLL, DEFAULT_DBX_LIST_VIEW_META_ICON, DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING, DEFAULT_DBX_PDF_MERGE_PAGE_EDITING, DEFAULT_DBX_PDF_MERGE_SIDECAR, DEFAULT_DBX_PROMPT_CONFIRM_DIALOG_CONFIG, DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_SIDENAV_MENU_ICON, DEFAULT_DBX_STYLE_CONFIG_TOKEN, DEFAULT_DBX_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_WEB_FILE_PREVIEW_SERVICE_DIALOG_WITH_COMPONENT_FUNCTION, DEFAULT_DBX_WEB_FILE_PREVIEW_SERVICE_PREVIEW_COMPONENT_FUNCTION, DEFAULT_ERROR_POPOVER_KEY, DEFAULT_ERROR_WIDGET_CODE, DEFAULT_FILTER_POPOVER_KEY, DEFAULT_IMAGE_BITMAP_TO_BLOB_ENCODER, DEFAULT_IMAGE_JPEG_QUALITY, DEFAULT_LIST_GRID_SIZE_CONFIG, DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_LOADING_PROGRESS_DIAMETER, DEFAULT_PDF_MERGE_ACCEPT, DEFAULT_PDF_MERGE_PAGE_GROUP_KEY, DEFAULT_SCREEN_MEDIA_SERVICE_CONFIG, DEFAULT_SNACKBAR_DIRECTIVE_DURATION, DEFAULT_TWO_COLUMNS_MIN_RIGHT_WIDTH, DEFAULT_VALUE_LIST_VIEW_CONTENT_COMPONENT_TRACK_BY_FUNCTION, DbxAccordionHeaderHeightDirective, DbxActionConfirmDirective, DbxActionDialogDirective, DbxActionErrorDirective, DbxActionKeyTriggerDirective, DbxActionLoadingContextDirective, DbxActionModule, DbxActionPopoverDirective, DbxActionSnackbarComponent, DbxActionSnackbarDirective, DbxActionSnackbarErrorDirective, DbxActionSnackbarModule, DbxActionSnackbarService, DbxActionTransitionSafetyDirective, DbxActionUIRouterTransitionSafetyDialogComponent, DbxAnchorComponent, DbxAnchorContentComponent, DbxAnchorIconComponent, DbxAnchorListComponent, DbxAngularRouterSegueAnchorComponent, DbxAvatarComponent, DbxAvatarViewComponent, DbxAvatarViewService, DbxAvatarViewServiceConfig, DbxBarDirective, DbxBarHeaderComponent, DbxBarLayoutModule, DbxBasicLoadingComponent, DbxBodyDirective, DbxButtonComponent, DbxButtonModule, DbxButtonSpacerDirective, DbxCardBoxComponent, DbxCardBoxContainerDirective, DbxCardBoxLayoutModule, DbxChipDirective, DbxChipListComponent, DbxClickToCopyTextComponent, DbxClickToCopyTextDirective, DbxColorDirective, DbxColorService, DbxColorServiceConfig, DbxColumnLayoutModule, DbxCompactDirective, DbxCompactLayoutModule, DbxContentBorderDirective, DbxContentBoxDirective, DbxContentContainerDirective, DbxContentDirective, DbxContentElevateDirective, DbxContentLayoutModule, DbxContentPageDirective, DbxContentPitDirective, DbxDetachContentComponent, DbxDetachControlButtonsComponent, DbxDetachController, DbxDetachControlsComponent, DbxDetachInitDirective, DbxDetachInteractionModule, DbxDetachOutletComponent, DbxDetachOverlayComponent, DbxDetachService, DbxDetachWindowState, DbxDetailBlockComponent, DbxDetailBlockHeaderComponent, DbxDialogContentCloseComponent, DbxDialogContentDirective, DbxDialogContentFooterComponent, DbxDialogInteractionModule, DbxDialogModule, DbxDownloadBlobButtonComponent, DbxDownloadTextViewComponent, DbxEmbedComponent, DbxErrorComponent, DbxErrorDefaultErrorWidgetComponent, DbxErrorDetailsComponent, DbxErrorPopoverComponent, DbxErrorSnackbarComponent, DbxErrorSnackbarService, DbxErrorViewComponent, DbxErrorWidgetService, DbxErrorWidgetViewComponent, DbxFileUploadActionCompatable, DbxFileUploadActionSyncDirective, DbxFileUploadAreaComponent, DbxFileUploadButtonComponent, DbxFileUploadComponent, DbxFilterInteractionModule, DbxFilterPopoverButtonComponent, DbxFilterPopoverComponent, DbxFilterWrapperComponent, DbxFlagComponent, DbxFlagLayoutModule, DbxFlagPromptComponent, DbxFlexGroupDirective, DbxFlexLayoutModule, DbxFlexSizeDirective, DbxHelpContextDirective, DbxHelpContextService, DbxHelpViewListComponent, DbxHelpViewListEntryComponent, DbxHelpViewPopoverButtonComponent, DbxHelpViewPopoverComponent, DbxHelpWidgetService, DbxHelpWidgetServiceConfig, DbxIconButtonComponent, DbxIconButtonModule, DbxIconItemComponent, DbxIconSpacerDirective, DbxIconTileComponent, DbxIconTileDirective, DbxIfSidenavDisplayModeDirective, DbxIframeComponent, DbxInjectionDialogComponent, DbxInteractionModule, DbxIntroActionSectionComponent, DbxLabelBlockComponent, DbxLayoutModule, DbxLinkComponent, DbxLinkifyComponent, DbxLinkifyService, DbxLinkifyServiceConfig, DbxListAccordionViewComponentImportsModule, DbxListComponent, DbxListEmptyContentComponent, DbxListGridViewComponentImportsModule, DbxListInternalContentDirective, DbxListItemAnchorModifierDirective, DbxListItemDisableRippleModifierDirective, DbxListItemIsSelectedModifierDirective, DbxListModifierModule, DbxListModule, DbxListTitleGroupDirective, DbxListView, DbxListViewMetaIconComponent, DbxListViewWrapper, DbxListWrapperComponentImportsModule, DbxLoadingComponent, DbxLoadingErrorDirective, DbxLoadingModule, DbxLoadingProgressComponent, DbxModelObjectStateService, actions as DbxModelStateActions, model_actions as DbxModelStateModelActions, DbxModelTrackerService, DbxModelTypesService, DbxModelViewTrackerStorage, DbxNavbarComponent, DbxNumberWithLimitComponent, DbxOneColumnComponent, DbxOneColumnLayoutModule, DbxPagebarComponent, DbxPartialPresetFilterListComponent, DbxPartialPresetFilterMenuComponent, DbxPdfMergeEditorComponent, DbxPdfMergeEditorFileUploadComponent, DbxPdfMergeEditorFileUploadHasStateDirective, DbxPdfMergeEditorFileUploadValidatorDirective, DbxPdfMergeEditorStore, DbxPdfMergeEditorStoreDirective, DbxPdfMergeEntryComponent, DbxPdfMergeImportComponent, DbxPdfMergeListComponent, DbxPdfMergePageComponent, DbxPdfMergePageListComponent, DbxPdfMergeUploadActionDirective, DbxPdfMergeUploadButtonDirective, DbxPdfMergeUploadDialogComponent, DbxPdfPreviewComponent, DbxPopoverCloseButtonComponent, DbxPopoverComponent, DbxPopoverComponentController, DbxPopoverContentComponent, DbxPopoverController, DbxPopoverControlsDirective, DbxPopoverCoordinatorComponent, DbxPopoverCoordinatorService, DbxPopoverHeaderComponent, DbxPopoverInteractionContentModule, DbxPopoverInteractionModule, DbxPopoverScrollContentDirective, DbxPopoverService, DbxPopupComponent, DbxPopupComponentController, DbxPopupContentComponent, DbxPopupControlButtonsComponent, DbxPopupController, DbxPopupControlsComponent, DbxPopupCoordinatorComponent, DbxPopupCoordinatorService, DbxPopupInteractionModule, DbxPopupService, DbxPopupWindowState, DbxPresetFilterListComponent, DbxPresetFilterMenuComponent, DbxProgressBarButtonComponent, DbxProgressButtonsModule, DbxProgressSpinnerButtonComponent, DbxPromptBoxDirective, DbxPromptComponent, DbxPromptConfirm, DbxPromptConfirmButtonDirective, DbxPromptConfirmComponent, DbxPromptConfirmDialogComponent, DbxPromptConfirmDirective, DbxPromptModule, DbxPromptPageComponent, DbxReadableErrorModule, DbxResizedDirective, DbxRouterAnchorModule, DbxRouterLayoutModule, DbxRouterSidenavModule, DbxRouterWebProviderConfig, DbxScreenMediaService, DbxScreenMediaServiceConfig, DbxSectionComponent, DbxSectionHeaderComponent, DbxSectionLayoutModule, DbxSectionPageComponent, DbxSelectionValueListViewComponent, DbxSelectionValueListViewComponentImportsModule, DbxSelectionValueListViewContentComponent, DbxSetStyleDirective, DbxSidenavButtonComponent, DbxSidenavComponent, DbxSidenavPageComponent, DbxSidenavPagebarComponent, DbxSpacerDirective, DbxStepBlockComponent, DbxStructureDirective, DbxStructureModule, DbxStyleBodyDirective, DbxStyleDirective, DbxStyleLayoutModule, DbxStyleService, DbxSubSectionComponent, DbxTextChipsComponent, DbxTextColorDirective, DbxTextModule, DbxTwoBlockComponent, DbxTwoColumnBackDirective, DbxTwoColumnColumnHeadDirective, DbxTwoColumnComponent, DbxTwoColumnContextDirective, DbxTwoColumnFullLeftDirective, DbxTwoColumnLayoutModule, DbxTwoColumnRightComponent, DbxTwoColumnSrefDirective, DbxTwoColumnSrefShowRightDirective, DbxUIRouterSegueAnchorComponent, DbxUnitedStatesAddressComponent, DbxValueListAccordionViewComponent, DbxValueListAccordionViewContentComponent, DbxValueListAccordionViewContentGroupComponent, DbxValueListGridSizeDirective, DbxValueListGridViewComponent, DbxValueListGridViewContentComponent, DbxValueListGridViewContentGroupComponent, DbxValueListItemModifier, DbxValueListItemModifierDirective, DbxValueListView, DbxValueListViewComponent, DbxValueListViewComponentImportsModule, DbxValueListViewContentComponent, DbxValueListViewContentGroupComponent, DbxValueListViewGroupDelegate, DbxWebFilePreviewComponent, DbxWebFilePreviewService, DbxWebModule, DbxWebPageTitleInfoDirective, DbxWebPageTitleService, DbxWidgetListGridComponent, DbxWidgetListGridViewComponent, DbxWidgetListGridViewItemComponent, DbxWidgetService, DbxWidgetViewComponent, DbxWindowKeyDownListenerDirective, DbxZipBlobPreviewComponent, DbxZipPreviewComponent, PDF_MERGE_PAGE_ID_SEPARATOR, PDF_MERGE_PAGE_TAG_KEY, PDF_MERGE_RESULT_MIME_TYPE, PDF_MERGE_SIDECAR_DESCRIPTION, PDF_MERGE_SIDECAR_FILE_NAME, PDF_MERGE_SIDECAR_VERSION, PopoverPositionStrategy, PopupGlobalPositionStrategy, SCREEN_MEDIA_WIDTH_TYPE_SIZE_MAP, SIDE_NAV_DISPLAY_MODE_ORDER, SideNavDisplayMode, TRACK_BY_MODEL_ID, TRACK_BY_MODEL_KEY, TwoColumnsContextStore, UNKNOWN_ERROR_WIDGET_CODE, addConfigToValueListItems, allDbxModelViewTrackerEventModelKeys, allDbxModelViewTrackerEventSetModelKeys, asPdfMergeFile, attachPdfMergeSidecar, buildPdfMergeEntriesFromSidecar, buildPdfMergeEntry, buildPdfMergeEntrySync, buildPdfMergePagePlan, catchErrorServerParams, classifyPdfMergeFile, compactModeFromInput, compareScreenMediaWidthTypes, compressImageFile, convertServerErrorParams, convertToPOJOServerErrorResponse, convertToServerErrorResponse, copyToClipboardFunction, dbxColorBackground, dbxCuratedColorConfigForString, dbxListAccordionViewComponentImportsAndExports, dbxListGridViewComponentImportsAndExports, dbxPresetFilterMenuButtonIconObservable, dbxPresetFilterMenuButtonTextObservable, dbxStyleClassCleanSuffix, dbxThemeColorCssToken, dbxThemeColorCssTokenVar, dbxThemeColorCssVariable, dbxThemeColorCssVariableVar, dbxValueListItemDecisionFunction, dbxValueListItemKeyForItemValue, dbxWebDefaultPageTitleDelegate, dbxZipBlobPreviewEntryTreeFromEntries, defaultDbxModelViewTrackerStorageAccessorFactory, defaultDbxValueListViewGroupDelegate, defaultDbxValueListViewGroupValuesFunction, disableRightClickInCdkBackdrop, entryIdForPdfMergePageId, fileAcceptFilterTypeStringArray, fileAcceptFunction, fileAcceptString, fileArrayAcceptMatchFunction, flattenAccordionGroups, formatPdfMergeEntrySize, index as fromDbxModel, injectCopyToClipboardFunction, injectCopyToClipboardFunctionWithSnackbarMessage, isDbxColorConfig, listItemModifier, makeDbxActionSnackbarDisplayConfigGeneratorFunction, makePdfMergePageId, makePdfMergeSidecar, makePdfMergeSidecarPageTag, mapCompactModeObs, mapValuesToValuesListItemConfigObs, mergePdfMergeEntries, index$1 as onDbxModel, openEmbedDialog, openIframeDialog, openPdfPreviewDialog, openZipPreviewDialog, overrideClickElementEffect, pdfMergePageGroupKeyForSlotId, provideDbxDetachController, provideDbxFileUploadActionCompatable, provideDbxHelpServices, provideDbxLinkify, provideDbxListView, provideDbxListViewWrapper, provideDbxModelService, provideDbxPdfMergeEditorConfig, provideDbxPdfMergeEditorPreserveEntriesOnSlotDestroy, provideDbxProgressButtonGlobalConfig, provideDbxPromptConfirm, provideDbxRouterWebAngularRouterProviderConfig, provideDbxRouterWebUiRouterProviderConfig, provideDbxScreenMediaService, provideDbxStyleService, provideDbxValueListView, provideDbxValueListViewGroupDelegate, provideDbxValueListViewModifier, provideDbxWebFilePreviewServiceEntries, provideDbxWebPageTitleService, provideTwoColumnsContext, readPdfMergeEntryPageMetas, readPdfMergePageTag, readPdfMergeSidecar, registerHelpContextKeysWithDbxHelpContextService, resizeSignal, resolveSideNavDisplayMode, sanitizeDbxDialogContentConfig, screenMediaWidthTypeIsActive, splitPdfMergeSidecarDocuments, trackByModelKeyRef, trackByUniqueIdentifier, validatePdfMergeEntry, writePdfMergePageTag };
19716
21309
  //# sourceMappingURL=dereekb-dbx-web.mjs.map